Skip to content

fix: resolve project context for untitled query files#1842

Open
shreyastelkar wants to merge 9 commits intomasterfrom
fix/untitled-query-execution
Open

fix: resolve project context for untitled query files#1842
shreyastelkar wants to merge 9 commits intomasterfrom
fix/untitled-query-execution

Conversation

@shreyastelkar
Copy link
Copy Markdown
Contributor

@shreyastelkar shreyastelkar commented Mar 19, 2026

Summary

Related issue: #1827

Fixes untitled query files silently failing in multi-project workspaces.

On multi-project workspace:

Before:

image

After (shows codelens to select project):

image

Broken flows

  1. "Create new SQL file" → Cmd+Enter → nothing happens. createSqlFile picks a project but doesn't persist it to workspace state. executeSQL reads dbtPowerUser.projectSelected → empty → findDBTProject(untitledUri) can't match → silent no-op.

  2. File > New File → jinja-sql → Cmd+Enter → nothing happens. No project is persisted and no fallback exists to prompt the user.

In single-project workspaces both flows already work. In multi-project, they only work if the user previously clicked the "Project: ..." codelens (the only thing that writes dbtPowerUser.projectSelected on main).

Changes

  • createSqlFile persists the selected project to workspace state after picking
  • ensureProjectForUntitledUri fallback in RunModel for the File > New File flow — auto-selects single project or prompts in multi-project
  • findDBTProject now calls resolveProjectUri internally — every caller gets untitled URI support automatically, resolveProjectUri stays private
  • clearStaleProjectSelection clears workspace state when a project is removed (defensive)

Follow-up

Compiled dbt Preview also fails for untitled files in multi-project — tracked in #1845.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Persist the selected dbt project when creating an untitled SQL file and use that persisted project to resolve project context for compiling/executing queries from untitled editors. RunModel now depends on QueryManifestService and workspace state is cleared when its selected project root is unregistered.

Changes

Cohort / File(s) Summary
SQL Creation
src/commands/index.ts
Persist selected project (dbtPowerUser.projectSelected) via toProjectQuickPickItem(...) before opening an untitled SQL document.
Run / Execute Logic
src/commands/runModel.ts
RunModel now receives QueryManifestService; compileQueryOnActiveWindow() and executeQueryOnActiveWindow() are async; added ensureProjectForUntitledUri() to resolve/persist a project for untitled editors and abort when unresolved; untitled modelName set to "untitled".
DBT Project Resolution
src/dbt_client/dbtProjectContainer.ts
Added resolveProjectUri(uri: Uri) used by executeSQL() and compileQuery() to translate untitled URIs using persisted workspace state; added clearStaleProjectSelection() and hooked it into project unregistration handling.
DI Binding
src/inversify.config.ts
Updated RunModel factory binding to construct with DBTProjectContainer and QueryManifestService.
Quickpick Helper
src/quickpick/projectQuickPick.ts
Added exported toProjectQuickPickItem(project) and refactored project picker to use it.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Index as "commands/index.ts"
    participant Workspace as "Workspace State"
    participant Editor as "Untitled Editor"
    participant RunModel as "commands/runModel.ts"
    participant QueryManifest as "QueryManifestService"
    participant Container as "DBTProjectContainer"

    User->>Index: New Query / pick project
    Index->>Workspace: persist dbtPowerUser.projectSelected
    Index->>Editor: open untitled SQL

    User->>RunModel: Execute/Compile (untitled URI)
    RunModel->>Workspace: read dbtPowerUser.projectSelected
    alt no persisted project
        RunModel->>QueryManifest: getOrPickProjectFromWorkspace()
        QueryManifest-->>RunModel: selected project
        RunModel->>Workspace: persist dbtPowerUser.projectSelected
    end
    RunModel->>Container: compile/execute with resolveProjectUri(untitled URI)
    Container->>Workspace: read dbtPowerUser.projectSelected to resolve URI
    Container-->>RunModel: compile/execute result
    RunModel-->>User: show results
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • mdesmet
  • saravmajestic
  • anandgupta42
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: resolve project context for untitled query files' clearly and concisely summarizes the main change addressing the core issue that untitled files need project context to execute/compile.
Linked Issues check ✅ Passed The PR changes fully address issue #1827 by persisting project context for untitled files, enabling execution/preview without saving in both single and multi-project workspaces.
Out of Scope Changes check ✅ Passed All changes are directly scoped to resolving project context for untitled query files; no extraneous modifications detected across the five modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed PR description is comprehensive, including problem statement, solution overview, before/after screenshots, and broken flows explanation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/untitled-query-execution

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines 56 to 58
const query = window.activeTextEditor.document.getText();
if (query !== undefined) {
this.compileDBTQuery(fullPath, query);

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/runModel.ts`:
- Around line 48-60: In compileQueryOnActiveWindow(), ensure you check the
boolean result of ensureProjectForUntitledUri() and bail out if it returns false
(like executeQueryOnActiveWindow does) before calling compileDBTQuery; i.e.,
after detecting an untitled URI call ensureProjectForUntitledUri(), and if it
returns false return early so compileDBTQuery(fullPath, query) is not invoked
with an unresolved/untitled project.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1a1c6f19-df8c-47b1-b66f-c42c9d2dd036

📥 Commits

Reviewing files that changed from the base of the PR and between b0a58e4 and f5d94b6.

📒 Files selected for processing (4)
  • src/commands/index.ts
  • src/commands/runModel.ts
  • src/dbt_client/dbtProjectContainer.ts
  • src/inversify.config.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/commands/runModel.ts (1)

82-84: Minor: Error message mentions "executing" but method is also used for compilation.

The error message references "executing query" but ensureProjectForUntitledUri() is called from both compileQueryOnActiveWindow() and executeQueryOnActiveWindow(). Consider a more generic message.

Suggested fix
     if (!project) {
       window.showErrorMessage(
-        "Unable to find dbt project for executing query.",
+        "Unable to find dbt project for this query.",
       );
       return false;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/runModel.ts` around lines 82 - 84, The error message in
ensureProjectForUntitledUri() is too specific ("Unable to find dbt project for
executing query.") even though this helper is used by both
compileQueryOnActiveWindow() and executeQueryOnActiveWindow(); update the
message to be generic (e.g., "Unable to find dbt project for this operation.")
by changing the string passed to window.showErrorMessage in
ensureProjectForUntitledUri() so it accurately covers both compilation and
execution calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/commands/runModel.ts`:
- Around line 82-84: The error message in ensureProjectForUntitledUri() is too
specific ("Unable to find dbt project for executing query.") even though this
helper is used by both compileQueryOnActiveWindow() and
executeQueryOnActiveWindow(); update the message to be generic (e.g., "Unable to
find dbt project for this operation.") by changing the string passed to
window.showErrorMessage in ensureProjectForUntitledUri() so it accurately covers
both compilation and execution calls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe004b6a-765d-4b24-9dd1-d687768704df

📥 Commits

Reviewing files that changed from the base of the PR and between f5d94b6 and 456836d.

📒 Files selected for processing (1)
  • src/commands/runModel.ts

this.executeSQL(modelPath, query, modelName);
}

runModelOnNodeTreeItem(type: RunModelType) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: For untitled URIs, modelPath.fsPath returns something like "untitled:Untitled-1" on macOS/Linux, so path.basename(modelPath.fsPath, ".sql") produces "untitled:Untitled-1" as the modelName. This gets passed to executeSQLOnQueryPanel as the query tab name — probably shows up as an ugly label in the results panel.

Not a correctness issue (execution still works), but could be a minor UX nit. Consider a fallback like:

const modelName = modelPath.scheme === "untitled"
  ? "untitled"
  : path.basename(modelPath.fsPath, ".sql");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already addressed — see lines 129-132 in the current code:

const modelName =
  modelPath.scheme === "untitled"
    ? "untitled"
    : path.basename(modelPath.fsPath, ".sql");

this.setToWorkspaceState("dbtPowerUser.projectSelected", undefined);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: I verified that DBTProject.contains() uses uri.fsPath === this.projectRoot.fsPath || uri.fsPath.startsWith(this.projectRoot.fsPath + path.sep) — so an exact === comparison here is correct and consistent with how projects are keyed. Just documenting the verification in case future reviewers wonder why it's not startsWith.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch documenting this. The exact === match on fsPath is intentional and consistent with how DBTWorkspaceFolder.contains() keys projects.

Copy link
Copy Markdown

@setu-altimateai setu-altimateai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #1842 Review — Fix project context for untitled query files

Summary: Well-scoped fix for a real UX bug. The root cause analysis is correct (URI scheme mismatch → silent ?. no-op), the fix is layered correctly across the call stack, and the third commit's refactors (stale state cleanup, toProjectQuickPickItem extraction) show good follow-through on the review cycle. No blockers.


Files reviewed

src/commands/index.ts              +11/-1  (persist project on createSqlFile)
src/commands/runModel.ts           +51/-6  (ensureProjectForUntitledUri, async guards)
src/dbt_client/dbtProjectContainer.ts  +51/-10  (resolveProjectUri, clearStaleProjectSelection)
src/inversify.config.ts             +4/-1  (wire QueryManifestService into RunModel)
src/quickpick/projectQuickPick.ts  +14/-7  (extract toProjectQuickPickItem)

What looks good

Root cause fix is correct. The untitled: scheme can't be matched by findDBTProject because DBTProject.contains() compares fsPath values — untitled URIs have no meaningful fsPath. Storing the selected project in workspace state at creation time and resolving it on demand is the right approach.

resolveProjectUri returning project root works. I checked DBTProject.contains() — it uses uri.fsPath === this.projectRoot.fsPath || uri.fsPath.startsWith(this.projectRoot.fsPath + sep), so passing the project root URI directly matches the exact-root case. Left a thought comment on this.

Uri.file(raw.fsPath || raw.path) reconstruction. Correct approach — workspace state deserializes Uri as plain JSON, losing the prototype chain. Reconstructing via Uri.file() ensures .fsPath works on retrieval.

clearStaleProjectSelection on unregister. Good defensive addition. The === comparison is correct given how project roots are keyed (verified against DBTProject.contains() impl).

compileQuery untitled guard. The second commit fixing the discarded return value from ensureProjectForUntitledUri() in compileQueryOnActiveWindow is a real bug catch — without it, a cancelled project picker would still proceed to compile.

toProjectQuickPickItem extraction. Clean DRY refactor. The {label, description, uri} shape was duplicated at 3 call sites — good to consolidate before this adds more.

DI wiring. RunModel → QueryManifestService → DBTProjectContainer is a clean DAG with no circular dependency, as noted in the PR description.


Issues

One nit inline on modelName for untitled URIs (see inline comment) — UX-only, not correctness.


Checklist

Area Status
Root cause addressed Pass
Single-project workspace Pass (auto-selects, no prompt)
Multi-project workspace Pass (prompts on first use, persists)
createSqlFile path Pass
File > New File + jinja-sql path Pass (ensureProjectForUntitledUri fallback)
Compile path Pass (+ second commit bail-out fix)
Stale state on project removal Pass
DI wiring Pass
No circular deps Pass

lgtm — ship it.

Untitled files created via 'New query' or manually silently failed to
execute or compile because the selected project was never persisted to
workspace state, and executeSQL/compileQuery could not resolve untitled
URIs to a dbt project.

Changes:
- createSqlFile now stores the selected project in workspace state
- Extract resolveProjectUri() helper with safe Uri reconstruction from
  deserialized workspace state; apply to both executeSQL and compileQuery
- Add ensureProjectForUntitledUri() fallback in RunModel that validates
  stored project still exists (clears stale state) and falls back to
  getOrPickProjectFromWorkspace for untitled files not created via
  createSqlFile
- Extract toProjectQuickPickItem() to eliminate duplicated shape across
  3 call sites
- Invalidate dbtPowerUser.projectSelected when a project is unregistered
- Use clean 'untitled' modelName instead of raw fsPath for untitled URIs
- Wire QueryManifestService into RunModel via DI

Fixes #1827
@shreyastelkar shreyastelkar force-pushed the fix/untitled-query-execution branch from 8c366b5 to 45646e1 Compare March 19, 2026 04:10
…eview

The 'Compiled dbt Preview' command (dbtPowerUser.sqlPreview) went through
SqlPreviewContentProvider which assumed file: scheme for document lookup
and fell back to readFileSync — causing ENOENT for untitled query files.

Changes:
- requestCompilation() now tries untitled: scheme when looking up the
  source document, uses resolveProjectUri for project resolution, and
  avoids disk reads for untitled files
- onDidChangeTextDocument handler also checks untitled: scheme so
  live-update works when typing in untitled files with preview open
- Make resolveProjectUri non-private so SqlPreviewContentProvider can
  use it for untitled URI resolution
Move query/URI capture after the ensureProjectForUntitledUri() await
in both compileQueryOnActiveWindow and executeQueryOnActiveWindow.
If the user switches editors while the project picker is open, we
now read from whichever editor is active when the picker resolves.
Move untitled URI resolution inside findDBTProject so every caller
gets untitled support automatically. resolveProjectUri stays private —
no public API surface change. executeSQL and compileQuery simplified
to plain findDBTProject calls.
Workspace state deserializes Uri as a plain JS object. The .fsPath
getter lives on the Uri prototype and is lost during serialization,
so raw.fsPath was always undefined. Use .path directly.
if (!selectedProject?.uri) {
return;
}
if (selectedProject.uri.path === removedRoot.fsPath) {

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid catch. Fixed — now reconstructs via Uri.file(selectedProject.uri.path).fsPath before comparing to removedRoot.fsPath, so both sides use OS-native separators.

…Windows

Uri.path uses forward slashes (/C:/Users/project) while Uri.fsPath
uses OS-native separators (C:\Users\project). Reconstruct via
Uri.file() before comparing to ensure cross-platform consistency.
Tests multi-project workspace scenarios:
- untitled URI resolves to stored project from workspace state
- file-scheme URIs bypass workspace state lookup
- untitled URI returns undefined when no project stored
- stale project selection is cleared on unregister
// Persist project selection so untitled files can resolve
// project context during query execution and compilation
this.dbtProjectContainer.setToWorkspaceState(
"dbtPowerUser.projectSelected",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This already has some significance in other screens. It was not required before. Any way to avoid this?

I thought it worked like this before:

if single project. -> use that project, if multiple projects -> show dropdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants