# snowflake_stage_orphaned_files

> 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.

Source: https://zop.dev/integrations/snowflake/recommendations/snowflake-stage-orphaned-files
Updated: 2026-08-19

---

## 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

```sql
LIST @my_stage;

SELECT stage_name, last_load_time, row_count
FROM snowflake.account_usage.copy_history
WHERE 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.
