Internal stages holding files no pipeline has consumed
What does ZopNight detect here?
An internal Snowflake stage with no COPY INTO activity in 30 days is holding staged files that no pipeline is consuming, at full storage rates. Note the honest limit: the per-stage bytes metric is currently a no-op, so this fires on copy inactivity alone.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | snowflake_stage_orphaned_files |
| Category | orphan |
| Severity | low |
| Metric | none — pure configuration read |
| Source | signals/orphan/snowflake-stage-purge.yaml |
Where it applies
What this rule can and cannot see
The intent is to find stages accumulating bytes with no pipeline consuming them. The implementation is currently narrower than the intent, and it is worth being direct about that.
Snowflake’s STAGE_STORAGE_USAGE_HISTORY view does not expose per-stage bytes. It reports
account-level stage storage only. So the bytes half of the signal is a no-op today. The
rule fires purely on “no COPY INTO in 30 days”, and the size of the stage is unknown to it.
That means a stage flagged here might hold 2 GB or 2 TB. The finding tells you a pipeline stopped
consuming, not how much it is costing you. Closing that gap needs a per-stage LIST-driven
discovery path, which does not exist yet.
Why files accumulate
COPY INTO does not remove source files by default. Unless PURGE = TRUE is set, every file
ever loaded stays in the stage indefinitely, billed as Snowflake storage.
A pipeline that has run daily for two years without PURGE has two years of loaded files sitting
in the stage, all of them already ingested and none of them ever read again.
The other common case is a decommissioned pipeline: the producer keeps writing files to the stage, the consumer was turned off, and nothing signals the mismatch.
Running LIST on the stage, then COPY_HISTORY
LIST @my_stage;
SELECT stage_name, last_load_time, row_countFROM snowflake.account_usage.copy_historyWHERE last_load_time > DATEADD(day,-30,CURRENT_TIMESTAMP())ORDER BY last_load_time DESC;LIST is the only way to see per-stage contents, which is exactly why the automated bytes signal
is not available.
Fixing it
Set PURGE = TRUE on the COPY INTO so future loads clean up after themselves, then remove the
historical backlog with REMOVE @my_stage/path. Confirm the files have genuinely been loaded
before removing anything. COPY_HISTORY is the record that tells you.