Snowflake World Tour hits your city

See how leading teams deploy agents at scale. Find a stop near you.

Snowflake for Developers/Developer Blog/My Coding Agent Won't Stop Hallucinating Table Columns. Why?

My Coding Agent Won't Stop Hallucinating Table Columns. Why?

Luke Hartzell, Gilberto Hernandez

You're in VS Code, halfway through a dbt model. You ask the coding agent in the sidebar to add a monthly revenue rollup. It writes SQL that looks right on paper, joining a few tables you recognize, and summing a column called order_total. The problem? There is no order_total column.

It turns out the correct column is actually called amount. You know this because you personally double-checked, whether directly in the database or using some different tool, after the dbt model already failed to build.

So you do the thing everyone does. You go into your database, copy the column list, paste it back into the agent chat, and ask again. This time the query is correct. But twenty minutes later you're working in a different dbt model, with different tables, and the agent has forgotten all of the previous context. You're back to square one.

Your agent has excellent context on the repo holding your model's declarations, but is missing all of the important context on the actual databases being used by the model. Those are two different kinds of context, and almost every tool in this space only solves the first one.

That gap has a name: account context. And it's the one almost nothing in your editor solves. This post is about why the gap exists and how to close it: not how to model your data, but how any of it reaches your agent in the first place.

Two kinds of context

In a world where "context is king", conflating (or overlooking) different kinds of context can mean the difference between a hallucination and a real insight from your data. In the scenario we described, the term "context" is used to refer to repo context and account context simultaneously. This conflation is exactly what hides the problem.

Repo context refers to context on your filesystem. Open files, tabs, the model you're editing, dbt_project.yml, your .sql files, git history. A coding agent gets all of this essentially for free, because it can read these files directly.

Account context refers to context on everything in your Snowflake data environment, like first-class data objects that your roles can actually see and use (databases, schemas, etc.), and visibility into runtime assets (compute warehouses, query histories, etc.). There are also things like temporal calculations: what row counts and null rates actually look like right now, or whether an object you're about to reference was dropped last week. Your agent cannot read your disk to find any of this account context. To do so, it'd need access to the environment containing that context; meaning it requires a connection, a role, a warehouse, permissions to run a query, and actually running queries.

So we can further break down account context into two sub-slices of context, the data-asset slice and the runtime slice:

  • The data-asset slice is what semantic models capture (column details, metrics, relationships, synonyms, verified queries) and what Apache Ossie (incubating) exposes to compliant tools as a portable, governed representation.

  • The runtime slice comes through a live connection (the Snowflake VS Code extension, or a Snowflake-managed MCP server) and covers things like which role and warehouse are active, query history, and current object state.

The 'Practical approaches' section below covers how each of these mechanisms gets wired into your agent, and the tradeoffs of each.

Why isn't this problem already fixed?

The obvious question, then, is: why doesn't the editor just hand the relevant schema directly to the agent for you? It turns out people have been filing that exact request for years. For example, the MSSQL extension for VS Code has an open issue asking for exactly this: include the schema of the connected database in inline suggestions, because file-only context "is insufficient" for database work.

The answer turns out to be partly architectural (and kind of surprising). Microsoft's own docs for that extension state that VS Code's inline completion API is "effectively single-provider". This means that when Copilot is enabled, a third-party extension cannot contribute schema-aware ghost text at all. Their recommendation is to stop trying and use a chat participant instead.

That reframes the whole problem. If only a single extension can generate inline completions, and if it's tied to the general-purpose coding agent, then that also means the extension doesn't have a database connection. Anything that does have a connection has to reach you through chat, a code action, or a panel. So the account context you need for effective agentic work in your data environment is now completely out of reach.

The same docs are blunt about the failure mode when account context is lacking: Github Copilot "might hallucinate APIs or schema elements that don't exist, especially if context is limited." That is the invented column problem.

Another thing we kept seeing throughout the community is why the "two-window life" persists even for people who prefer the editor. For example, in Snowflake context, Snowsight workspaces are not database objects. They're stored in the web UI, which is why "just pull my worksheets into VS Code" had traditionally been something developers could not do, and why so many people described running Snowsight and VS Code side by side as their permanent, scrappy solution, rather than a migration in progress. For Snowflake developers, this has been addressed with agentic extensions for data-related work, covered in the next sections.

Practical approaches and tradeoffs

Before we get to the approaches that hold up in practice, it's worth calling out one that most people try first: pasting CREATE TABLE statements into agent instruction files, or directly into prompts themselves. Unfortunately, this is the most-upvoted advice in r/GithubCopilot threads on this topic.

We don't recommend this approach. Here's why:

(1) Statements go stale the moment someone ships a migration, and nothing will notify you

(2) Instructions burn context on every request, including ones that don't touch those tables

(3) What's in the file only covers the tables you remembered to paste, so the agent is confident about ten tables but hallucinating about the other four hundred

(4) Perhaps the most important: this approach puts your schema in a repo file, which is generally a poor security practice

The approaches below solve for all of this.

  1. Use the official Snowflake extension for VS Code.

    Snowflake publishes an official extension for Visual Studio Code, allowing you to access your account context directly from the editor. What's more is that the extension also includes CoCo directly within VS Code. Snowflake's own docs are direct about this being the preferred path for repo context and account context. (It also works in Cursor, which is built on the same extension model.)

    You wouldn't pick this option just because it's "better at writing SQL". You'd pick it because the extension holds the connection, meaning your role, your warehouse, your object explorer, and your query history are already in that sidebar, and a data coding agent is sitting in the same process with access to all of it.

  2. Bolt a Snowflake-managed MCP server onto Copilot or Cursor.

    MCP, the Model Context Protocol, is a standard way to give an agent tools it can call, so instead of being handed a stale schema the agent can go ask for the live one. This is a genuinely good approach if you're committed to a general-purpose agent that requires some access (but not direct access) to Snowflake.

    This is also where the two slices from earlier come back together. The MCP server surfaces the runtime slice, specifically the live connection, role, warehouse, and query history. It's the channel through which your agent reaches the semantic models we introduced above, so the data-asset context (columns, metrics, relationships, verified queries) travels with every tool call rather than getting pasted in ad hoc.

    The costs are setup and depth. You configure and authenticate it yourself. The agent gets governed tool calls that you define, against the configured Snowflake environment. Out of the box it supports multiple tool types, from AI execution to SQL scripting to a generic tool that can run just about anything you define in a stored procedure, UDF, or other object.

  3. Run an agent in the integrated terminal.
    The CoCo CLI is generally available, and a terminal inside VS Code is still inside VS Code. This gets you the full agent with Snowflake authentication, your existing role-based access control, and the built-in skills. What you give up is editor integration. No diff view in the editor for proposed file changes, no result grid you can click into, no button above the failing statement. You're copying between panes again, which is the smaller version of the problem you started with.

  4. Run the agent in a separate desktop app.
    CoCo Desktop is a native application with persistent project context as well as the integrated coding agent, and for longer multi-step work that persistence is the point. The cost is honest and simple: it's another application and one you may not be willing to switch to using.

What does this look like in action?

Let's make this tangible with a task that has no good answer in either kind of context alone: a stored procedure that checks a table for null values, duplicate keys, and stale data. You can't write it from repo context, because you don't know the columns. You can't write it in a one-off SQL file, because you need it in version control.

Prerequisites: VS Code or Cursor, and a Snowflake role with access to at least one database, schema, and warehouse. You do not need to install the CoCo CLI separately. If you install the Snowflake extension for VS Code, it also takes care of installing CoCo for you.

Install the extension from the Marketplace by searching for Snowflake and choosing the one with the blue check badge, then sign in from the Snowflake icon in the Activity Bar. Sign-in supports SSO, username and password, key pair, or an OAuth entry in your connections.toml. After sign-in the sidebar shows your account, your default role, the object explorer, and query history.

Open the chat panel and select CoCo at the top, or use Shift+Cmd+L on macOS and Shift+Ctrl+L on Windows and Linux. The session is scoped to the directory open in your editor, so chat history stays separated per workspace rather than pooling into one undifferentiated thread.

Attach context with @. Typing @ in the chat input pulls in workspace files and directories, so you can hand it your dbt_project.yml alongside the model you're editing. @ also searches your Snowflake account, so databases, schemas, and tables attach the same way a file does. That single detail is the whole argument for the extension: repo context and account context arrive through the same keystroke.

Ask for the procedure, describing what you want checked rather than how to check it:

Create a stored procedure called CHECK_DATA_QUALITY in COCO_VS_CODE_QUICKSTART_DB.MONITORING
that accepts a table name as input and checks for:

1. Null values in each column (report count and percentage)
2. Duplicate values in the first column (assumed to be the primary key)
3. Data freshness - flag if the most recent timestamp column value is older than 24 hours

The procedure should return a structured result with all findings.
Write it as a SQL file in my workspace.

Notice what comes back. A proposed file with a summarized diff, which you can accept whole, revert whole, or review at finer granularity before anything touches your workspace. The generated procedure is not shown here on purpose, because it depends on your table and I'm not going to paste code I haven't run against your schema. It's the interaction that's the point: it's a diff review, the same one you'd give a colleague's pull request.

The checks themselves are not crazy. For a single table the core of it looks like this, and this block is illustrative rather than runnable as written, since it assumes your column names:

SELECT COUNT(*) AS total_rows,
       COUNT(*) - COUNT(customer_id) AS null_customer_id,
       COUNT(*) - COUNT(DISTINCT event_id) AS duplicate_event_id,
       MAX(event_timestamp) AS latest_event
FROM COCO_VS_CODE_QUICKSTART_DB.MONITORING.CUSTOMER_EVENTS;

Notice that COUNT(column) skips nulls while COUNT(*) doesn't, which is what makes the subtraction work. That asymmetry is also the source of a class of silently wrong dbt models, where COUNT(*) counts rows with a null amount but SUM and AVG quietly ignore them, so your average is computed over a smaller denominator than your row count implies.

Ask CoCo appears above each SQL statement in the editor and opens chat with that statement already attached as context, so explaining or optimizing a specific query is one click. Fix with CoCo appears next to the error in the results pane when a query fails, and hands over both the failing statement and the error text. There's also an Analyze action on a successful result set that sends the rows themselves to chat. You stop copying error messages into a chat box, which is a small thing that happens forty times a day.

Skills work here too. Typing / in the chat input lists them, and they're the same skills across every CoCo surface. Custom skills in .cortex/skills/ in your workspace get picked up by the extension, so anything you built for the CLI already works.

A digression that has nothing to do with agents

This is unrelated to everything above, but you're going to hit it and the docs bury it. To render query results, the extension automatically runs DESC RESULT '<query_id>' in the background after every query you execute. That makes LAST_QUERY_ID() inaccurate. If you have SQL that depends on LAST_QUERY_ID(), it will behave differently in the extension than it does anywhere else, and nothing in the UI tells you why.
Review the docs: https://docs.snowflake.com/en/user-guide/vscode-ext

Settings you'll probably want to change

Four of the extension settings are directly relevant, and defaults are in parentheses:

  • snowflake.coco.enabled (enabled): turns the CoCo side panel off for all workspaces if your org wants the extension without the agent.
  • snowflake.coco.showAskCocoAboveStatement (enabled): hides the Ask CoCo action above statements if the inline button is noise for you.
  • snowflake.coco.cliPath (unset): points the extension at a specific CoCo executable already on your machine. Leave it alone unless you have a reason to change this.
  • Object Explorer: Search (enabled): this was the single most requested missing feature in the community thread on the extension, and it exists now.

Permissions are per-action, with allow once, allow for the session, allow always, and deny. Agent mode asks before every tool call, Plan mode produces a plan before touching anything, and Bypass mode skips the confirmations. Bypass is useful and you should think about which account you're pointed at before you select it.

What this actually is

An editor, an extension that holds a Snowflake connection, and an agent that can read both your files and your account through the same @. That's it, and none of it is a new tool you have to learn, because the agent is a panel in the window you already had open.

The model inventing a column that never existed...was never really its own problem. It was doing exactly what it was supposed to: inference over the information it had available. It just didn't have the information it actually needed. Give it this necessary account information through a connection like the Snowflake VS Code extension, and it won't be forced to always guess.

What to do next?

Get setup and started with real examples and step-by-step guides with our quickstart: https://www.snowflake.com/en/developers/guides/get-started-coco-vscode-extension/

Read more on the VS Code Extension from official docs: https://docs.snowflake.com/en/user-guide/vscode-ext

Updated Sep 15, 2026

This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances