| CREATE TASK | SCHEDULE takes ’ SECOND | MINUTE | HOUR’. USING CRON is refused by name — a cron expression means specific wall-clock times, and firing on an interval instead would be a schedule that is not the one asked for. WHEN is refused for the same reason in the other direction: a predicate that is never evaluated makes a conditional task unconditional. | 🟢 |
| Task graphs (AFTER) | | 🟢 |
| ALTER TASK RESUME / SUSPEND | | 🟢 |
| SHOW TASKS | | 🟢 |
| EXECUTE TASK | Runs the named task and everything downstream of it, as Snowflake does. A resumed root task also fires on its own interval. Its runs, and the runs of everything downstream, are readable through TASK_HISTORY(). | 🟢 |
| DROP TASK | | 🟢 |
| USING CRON is refused | | 🟢 |
| A manual task (no SCHEDULE, no AFTER) | The body is the whole statement after the task’s own AS, including a body carrying its own AS — a CREATE TABLE … AS SELECT, which is what a dbt model compiles to. It was not: the optional properties group was greedy, so a task with no WAREHOUSE swallowed the body’s first AS, stored a truncated statement, ran that, and reported SUCCEEDED. | 🟢 |
| EXECUTE TASK on a manual task | | 🟢 |
| A manual task with a CTAS body | | 🟢 |
| EXECUTE TASK runs the CTAS | | 🟢 |
| The CTAS body actually ran | Selected back from the table the body creates, so this cannot pass on a task that succeeded while running a different statement. Every other probe here is judged on whether the STATEMENT succeeded, which is one level away from whether anything happened. | 🟢 |
| A task body loads from a stage | | 🟢 |
| EXECUTE TASK runs the COPY INTO | | 🟢 |
| The task’s COPY INTO actually loaded | Counted from the table the task loaded into. The body used to go straight to duckdb, which answers COPY INTO with a syntax error at INTO — while the identical statement outside a task loaded fine. | 🟢 |
| A task body reads a stream | | 🟢 |
| EXECUTE TASK runs the stream read | | 🟢 |
| CREATE DBT PROJECT | | 🟢 |
| ENV_VARS on CREATE is refused, and says where it belongs | Snowflake puts a project’s environment in its env.yml and lets EXECUTE DBT PROJECT override it for one run; there is no ENV_VARS on CREATE. Storing it there would have the statement a caller actually runs ignore it, with the configuration sitting in plain sight in SHOW DBT PROJECTS. | 🟢 |
| An ENV_VARS key dbt would never see is refused | Snowflake requires ENV_VARS keys UPPERCASE and DBT_-prefixed. Accepting one dbt will never read is the silent kind of wrong: env_var() falls to its default and the models read the wrong thing with nothing failing. | 🟢 |
| SHOW DBT PROJECTS | | 🟢 |
| EXECUTE DBT PROJECT | dbt runs in THIS image, on argv, the same way duckdb does — no second service and no network hop between the statement and what executes it. The profile is generated under the name the PROJECT declares, so a project runs here without being edited to say an emulator-specific one. | 🟢 |
| dbt’s run_results is in the stage, at the path it named | GET resolves the file, so this fails if the archive was not written — the path is the one OUTPUT_ARCHIVE_URL reports, and the one a failing run names in its error. That is the point of leaving artefacts in the stage rather than returning them: a failed run has no result set to carry anything, and its evidence is what a caller most needs. | 🟢 |
| dbt really built the models | Read back from the model dbt was asked to build, so this cannot pass on a run that reported success and built nothing. | 🟢 |
| ENV_VARS reached the dbt process | Read back from a column the model fills with env_var(), so it cannot pass on a project that built while the value never arrived. | 🟢 |
| A dbt failure fails the QUERY | build is not one of run, test or deps, so it is refused by name. Snowflake made dbt errors query failures in October 2025 precisely so tasks could handle them: a failed run that returned Success = FALSE from a SUCCESSFUL statement let a task graph run its downstream nodes anyway. | 🟢 |
| A dbt task with no warehouse is refused | | 🟢 |
| A task body runs EXECUTE DBT PROJECT | | 🟢 |
| EXECUTE TASK runs the dbt project | | 🟢 |
| DROP DBT PROJECT | | 🟢 |
| The task’s stream read actually inserted | Counted from the table the task inserted into. The body used to go straight to duckdb, which has no table for a stream name at all. | 🟢 |
| CREATE STREAM | APPEND-ONLY, and it proves it rather than assuming it. DuckDB keeps no change log, so the stream remembers the first rowid it has not shown and a checksum of the rows it has. If a row before that point is updated or deleted the stream REFUSES TO BE READ, naming what happened — Snowflake would report those as DELETE and INSERT rows, and answering without them would silently drop the change. METADATA$ACTION is always INSERT for the same reason. | 🟢 |
| Reading a stream | | 🟢 |
| SYSTEM$STREAM_HAS_DATA | | 🟢 |
| SHOW STREAMS | | 🟢 |
| DROP STREAM | | 🟢 |
| Stored procedures | duckdb: Catalog Error: Table Function with name system$wait does not exist! | 🔴 |