Symptom
A downstream table stopped updating. The task that feeds it exists, nobody changed the SQL, and there is no alert because a task that never runs never fails. The usual causes are, in order of frequency:
- The task, or the root of its graph, is suspended.
- The runs are being skipped, usually by a
WHEN SYSTEM$STREAM_HAS_DATA(...)condition or overlap with a long previous run. - The runs fail and the graph auto-suspended itself after repeated failures.
- The stream the task reads went stale, so there is nothing valid to read.
Work through them in that order. All commands below run in a worksheet or SnowSQL with a role that can see the task.
1. Is the task started?
SHOW TASKS LIKE 'load_orders%' IN SCHEMA analytics.etl;
SELECT "name", "state", "schedule", "predecessors", "condition",
"warehouse", "owner"
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
state is either started or suspended. New tasks are always created suspended, and a CREATE OR REPLACE TASK from a deployment leaves the replacement suspended too. That is the most common reason a task "stopped after the release".
For a task graph, check every task, not just the one you care about. Only the root task has a schedule; children have predecessors. A child that is suspended never runs even if the root does.
SELECT name, state, predecessors
FROM TABLE(analytics.INFORMATION_SCHEMA.TASK_DEPENDENTS(
TASK_NAME => 'analytics.etl.load_orders_root',
RECURSIVE => TRUE));
Resume it
-- Standalone task
ALTER TASK analytics.etl.load_orders RESUME;
-- Whole graph: resumes the root and all dependents in one call
SELECT SYSTEM$TASK_DEPENDENTS_ENABLE('analytics.etl.load_orders_root');
Warning
Resuming a task that has been suspended for days does not replay the missed schedules. It runs at the next scheduled time only. If the task does incremental work from a stream, that is fine. If it loads "yesterday's partition" by date, you have to run the missed dates yourself.
2. What happened on recent runs?
INFORMATION_SCHEMA.TASK_HISTORY is near real-time and covers the last 7 days. SNOWFLAKE.ACCOUNT_USAGE.TASK_HISTORY keeps a year but lags (typically up to about 45 minutes).
SELECT name, state, scheduled_time, completed_time,
error_code, error_message, query_id
FROM TABLE(analytics.INFORMATION_SCHEMA.TASK_HISTORY(
SCHEDULED_TIME_RANGE_START => DATEADD('hour', -24, CURRENT_TIMESTAMP()),
TASK_NAME => 'LOAD_ORDERS',
RESULT_LIMIT => 100))
ORDER BY scheduled_time DESC;
To see only failures across the account in the last day:
SELECT database_name, schema_name, name, state, error_code,
error_message, scheduled_time
FROM SNOWFLAKE.ACCOUNT_USAGE.TASK_HISTORY
WHERE scheduled_time > DATEADD('day', -1, CURRENT_TIMESTAMP())
AND state IN ('FAILED', 'FAILED_AND_AUTO_SUSPENDED')
ORDER BY scheduled_time DESC;
Read the state column with this table:
| State | What it means | Next step |
|---|---|---|
| No rows at all | The task is suspended, or the schedule has not fired yet | Section 1; check the CRON time zone |
SCHEDULED stuck | Waiting for its time or for a predecessor | Check predecessors in the graph |
SKIPPED | Condition was false, or the previous run was still executing | Section 3 |
FAILED | The SQL or procedure errored | Open query_id in Query History, fix the SQL |
FAILED_AND_AUTO_SUSPENDED | Hit the consecutive-failure limit and suspended itself | Fix the error, then resume (section 4) |
CANCELLED | Cancelled manually or by a timeout | Check USER_TASK_TIMEOUT_MS |
Exact state names and columns can change between releases; check the TASK_HISTORY reference if a value here does not match what you see.
Time zone check
A CRON schedule without a time zone, or with the wrong one, looks like a task that never runs. Look at the schedule column:
ALTER TASK analytics.etl.load_orders_root SUSPEND;
ALTER TASK analytics.etl.load_orders_root
SET SCHEDULE = 'USING CRON 0 2 * * * UTC';
ALTER TASK analytics.etl.load_orders_root RESUME;
You must suspend the root task before you change the root or any child in its graph.
3. Runs are being skipped
The WHEN condition
SHOW TASKS LIKE 'load_orders' IN SCHEMA analytics.etl;
-- "condition" column, e.g. SYSTEM$STREAM_HAS_DATA('ANALYTICS.ETL.ORDERS_STREAM')
SELECT SYSTEM$STREAM_HAS_DATA('analytics.etl.orders_stream');
If this returns FALSE, the task is working as designed: there is nothing new in the stream. Then the problem is upstream. Check that the source table is actually receiving rows, and that the stream is on the right table:
SHOW STREAMS LIKE 'orders_stream' IN SCHEMA analytics.etl;
-- columns: table_name, mode, stale, stale_after
Overlap
By default a task does not start a new run while the previous one is still executing; the new schedule is skipped. A load that used to take 4 minutes on a 5-minute schedule and now takes 7 will skip every other run. Compare durations:
SELECT scheduled_time,
DATEDIFF('second', query_start_time, completed_time) AS run_seconds,
state
FROM TABLE(analytics.INFORMATION_SCHEMA.TASK_HISTORY(
TASK_NAME => 'LOAD_ORDERS', RESULT_LIMIT => 50))
ORDER BY scheduled_time DESC;
Fix the slow query or the warehouse size first. ALLOW_OVERLAPPING_EXECUTION = TRUE exists on root tasks, but two runs reading the same stream or merging into the same table is a fast route to duplicates. Leave it off unless the task is idempotent.
4. Runs fail, then the graph suspends itself
SUSPEND_TASK_AFTER_NUM_FAILURES suspends a task (or the whole graph, when set on the root) after a number of consecutive failed runs. Current documentation gives a default of 10. Check what is set for your task, since it can be overridden at account, database, schema or task level:
SHOW PARAMETERS LIKE 'SUSPEND_TASK_AFTER_NUM_FAILURES'
IN TASK analytics.etl.load_orders_root;
SHOW PARAMETERS LIKE 'USER_TASK_TIMEOUT_MS'
IN TASK analytics.etl.load_orders_root;
Common failure causes:
Error in error_message | Cause | Fix |
|---|---|---|
| Insufficient privileges to operate on task / execute task | Owner role lacks EXECUTE TASK (or EXECUTE MANAGED TASK for serverless) | GRANT EXECUTE TASK ON ACCOUNT TO ROLE etl_owner; |
| Warehouse does not exist or not authorized | Warehouse renamed, dropped or USAGE revoked | GRANT USAGE ON WAREHOUSE etl_wh TO ROLE etl_owner; |
| Object does not exist or not authorized | A table was recreated and grants lost, or owner role changed | Re-grant to the task owner role, not your own role |
| Statement reached its statement or warehouse timeout | Run exceeded USER_TASK_TIMEOUT_MS or STATEMENT_TIMEOUT_IN_SECONDS | Fix the query; raise the timeout only if the run is legitimately long |
| Stream ... has become stale | Stream offset fell out of retention | Section 5 |
Tasks run with the privileges of the owner role, not the user who created them. Test grants with that role:
USE ROLE etl_owner;
SHOW GRANTS TO ROLE etl_owner;
After fixing the cause, run once by hand and then resume:
EXECUTE TASK analytics.etl.load_orders_root;
-- For a graph that failed part-way, rerun from the failed task:
EXECUTE TASK analytics.etl.load_orders_root RETRY LAST;
SELECT SYSTEM$TASK_DEPENDENTS_ENABLE('analytics.etl.load_orders_root');
5. The stream is stale
A stream records an offset into the source table's change history. If nothing consumes it for longer than the table's data retention (Snowflake extends retention for streams up to MAX_DATA_EXTENSION_TIME_IN_DAYS, 14 days by default), the stream goes stale and cannot be read.
SHOW STREAMS IN SCHEMA analytics.etl;
SELECT "name", "table_name", "stale", "stale_after"
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
ORDER BY "stale_after";
There is no way to un-stale a stream. You recreate it and close the gap yourself.
Caution
CREATE OR REPLACE STREAMstarts tracking from now. Every change between the old offset and now is lost to the stream. Before recreating, record the last successful load time from TASK_HISTORY so you can backfill that window from the source table with a MERGE (or Time Travel, if the window is still within retention).
CREATE OR REPLACE STREAM analytics.etl.orders_stream
ON TABLE raw.sales.orders;
-- Backfill the gap idempotently, keyed on the business key
MERGE INTO analytics.core.orders t
USING (
SELECT * FROM raw.sales.orders
WHERE updated_at >= '2026-10-20 02:00:00'::timestamp_ntz
) s
ON t.order_id = s.order_id
WHEN MATCHED THEN UPDATE SET t.status = s.status, t.amount = s.amount,
t.updated_at = s.updated_at
WHEN NOT MATCHED THEN INSERT (order_id, status, amount, updated_at)
VALUES (s.order_id, s.status, s.amount, s.updated_at);
Recreating the stream also changes its owner and drops its grants if the role differs, so re-grant SELECT on it to the task owner role.
Prevent it next time
- Add a freshness check on the target table, not just on task failures. A suspended task produces no failure to alert on.
- In CI/CD, resume tasks explicitly after
CREATE OR REPLACE TASK, withSYSTEM$TASK_DEPENDENTS_ENABLEon each root. - Alert on
FAILED_AND_AUTO_SUSPENDEDfromACCOUNT_USAGE.TASK_HISTORY, or use a serverless alert (CREATE ALERT) that queries it. - Watch
stale_afteron every stream and page when it is less than a few days away. - Keep task owner roles as dedicated service roles, and grant objects to them rather than to people.
- Use
TASK_AUTO_RETRY_ATTEMPTSon the root for transient errors, and keepSUSPEND_TASK_AFTER_NUM_FAILURESset so a broken task stops burning credits.