Skip to content

fix: bubble real dbt show error instead of generic "Could not parse"#933

Open
sahrizvi wants to merge 1 commit into
mainfrom
fix/bubble-dbt-show-error-932
Open

fix: bubble real dbt show error instead of generic "Could not parse"#933
sahrizvi wants to merge 1 commit into
mainfrom
fix/bubble-dbt-show-error-932

Conversation

@sahrizvi

@sahrizvi sahrizvi commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Closes #932

(Supersedes #931 — same commits, branch renamed to drop the stale Jira key.)

Summary

altimate-dbt execute --query "..." (which calls execDbtShow in packages/dbt-tools/src/dbt-cli.ts) swallows the real error from dbt show and surfaces a misleading generic message:

{
  "error": "Could not parse dbt show output in any format (JSON, heuristic, or plain text). Got 0 JSON lines.",
  "fix": "Run: altimate-dbt doctor"
}

But running dbt show directly produces a clear actionable error, e.g.:

Runtime Error: Failed to read package: No dbt_project.yml found at expected path dbt_packages/dbt_utils/dbt_project.yml

Agents read "could not parse" as transient and retry alternate commands instead of bailing out — burns budget on a project that is structurally broken.

Root cause

packages/dbt-tools/src/dbt-cli.ts has two adjacent catch blocks in execDbtShow that swallow the run() rejection silently:

// ~line 224 — Tier 1 JSON attempt
try { const { stdout } = await run(args); lines = parseJsonLines(stdout) }
catch { lines = [] }            // ← discards err.stderr

// ~line 298 — Tier 3 plain-text fallback
try { ... await run(plainArgs) ... }
catch { /* fall through */ }    // ← also discards err.stderr

throw new Error("Could not parse dbt show output in any format (JSON, heuristic, or plain text). ...")

When execFile rejects with a non-zero exit, the error object carries .stderr and .stdout. Both catches discard them. The generic "Could not parse" message was designed for the case "dbt exited 0 but the output is unparseable" — but it currently fires for the "dbt actually crashed" case as well, conflating two very different failure modes.

Fix

  • Capture the execFile rejection on both tiers (primaryRunError, plainRunError).
  • Recover any partial JSON log lines from the failed run's stdout — dbt with --log-format json emits structured level: "error" events before exit.
  • Before the generic throw, call extractDbtError(...) which picks (in order): structured error event > primary stderr > plain stderr > exception message.
  • If extractDbtError returns anything, surface it as dbt show failed: <real msg>. Fall through to the generic message only when both runs exited 0.

Net: ~45 lines in dbt-cli.ts (helper + 2 small guard sites), 6 new test cases.

Scope

Limited to execDbtShow. execDbtCompile and execDbtCompileInline share the same catch { lines = [] } pattern but have additional fallback paths (manifest.json for compile, no-args plain-text retry for inline) that reduce caller impact. Worth addressing in a follow-up PR if telemetry shows masking there too — kept out of this PR to keep the diff focused.

Test plan

  • bun test test/dbt-cli.test.ts → 24/24 pass (18 pre-existing + 6 new)
  • Generic "Could not parse" message still surfaces when dbt show exits 0 but emits unparseable output (regression guard preserved)
  • Real stderr from a crashing dbt show surfaces in the thrown error
  • Structured level: "error" event preferred over raw stderr when both present
  • spawn ENOENT (no dbt binary) surfaces clearly
  • Reviewer: smoke against a real project with a corrupted dbt_packages/<pkg>/dbt_project.yml; expect to see the real "Failed to read package" message, not "Could not parse"

Links


Summary by cubic

Surface the real dbt error from execDbtShow instead of the generic “Could not parse…” message, so callers get actionable failures and agents stop wasting retries. Addresses #932.

  • Bug Fixes
    • Capture run() errors for both JSON and plain-text dbt show; parse JSON lines from failed stdout to recover structured error events.
    • Add extractDbtError to prefer structured JSON error > primary stderr > plain stderr > exception message; throw as dbt show failed: <message>.
    • Preserve the generic “Could not parse…” only when dbt exits 0 with unparseable output; change is limited to execDbtShow.
    • Tests: 6 new cases (stderr surfacing, structured-event preference, ENOENT fallback, exit-0 unparseable preserved).

Written for commit 064b0dd. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling for dbt show: surfaces actual dbt errors instead of generic "Could not parse" messages, prioritizes structured JSON error events, and includes stderr or execution failure details in thrown messages.
  • Tests

    • Added comprehensive tests covering dbt show failure scenarios and error selection behavior.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3adaf6a2-2a72-4040-bf31-7b8bdacb0952

📥 Commits

Reviewing files that changed from the base of the PR and between 62e21bf and 064b0dd.

📒 Files selected for processing (2)
  • packages/dbt-tools/src/dbt-cli.ts
  • packages/dbt-tools/test/dbt-cli.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/dbt-tools/test/dbt-cli.test.ts
  • packages/dbt-tools/src/dbt-cli.ts

📝 Walkthrough

Walkthrough

execDbtShow now records JSON-mode and plain-text run rejections, attempts to parse recovered stdout for JSON error events, and uses extractDbtError to surface a prioritized, human-readable dbt error instead of always throwing a generic parse-failure.

Changes

Real dbt error surfacing in execDbtShow

Layer / File(s) Summary
JSON-mode error capture and parsing
packages/dbt-tools/src/dbt-cli.ts
When the JSON-mode run() rejects, primaryRunError is captured and the rejection's stdout is parsed for JSON log lines, preserving potential error events from the captured output.
Plain-text fallback error capture and selection
packages/dbt-tools/src/dbt-cli.ts
Plain-text fallback execution is wrapped in try/catch to record plainRunError; after all parsing tiers fail, extractDbtError is called to select and throw the best available error message instead of unconditionally throwing generic parse failure.
Error extraction prioritization helper
packages/dbt-tools/src/dbt-cli.ts
extractDbtError helper selects an error message by priority: structured JSON "level: error" events from parsed lines, stderr from JSON-mode rejection, stderr from plain-text rejection, or fallback exception messages; paired with local ExecFileError interface for execFile rejection shape.
Error handling test cases
packages/dbt-tools/test/dbt-cli.test.ts
Five new test cases verify that real dbt stderr is surfaced, JSON error events take precedence, generic parse errors are suppressed on actual command failure, generic messages are preserved on exit-0 unparseable output, and fallback behavior on empty stderr.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through logs both terse and long,
Found the stderr hiding the honest song.
Now errors speak true instead of haze,
No more lost clues in parsing maze.
A carrot for clarity, and brighter days! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is comprehensive and well-structured, covering summary, root cause, fix, scope, and test plan; however, it is missing the required 'PINEAPPLE' marker at the top as specified in the template for AI-generated contributions. Add 'PINEAPPLE' at the very top of the PR description before all other content, as required by the template for AI-generated contributions.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: surfacing real dbt show errors instead of a generic 'Could not parse' message, which matches the primary objective of the changeset.
Linked Issues check ✅ Passed The code changes fully address all requirements from issue #932: capturing run() rejections, recovering JSON log lines from failed stdout, implementing extractDbtError helper with proper precedence order, and surfacing real errors while preserving generic fallback for exit-0 unparseable output.
Out of Scope Changes check ✅ Passed All changes are scoped to execDbtShow as stated; the PR intentionally excludes execDbtCompile and execDbtCompileInline for a follow-up, with +61/-4 lines in dbt-cli.ts and +67/-0 test lines staying focused on the stated objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bubble-dbt-show-error-932

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.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/dbt-tools/test/dbt-cli.test.ts">

<violation number="1" location="packages/dbt-tools/test/dbt-cli.test.ts:205">
P3: Duplicate test: "preserves generic 'Could not parse' when dbt exited 0 but output unparseable" tests the exact same scenario as the existing "Tier 3: throws with helpful message when all tiers fail" test. Both use an identical mock (cb(null, "some unparseable output", "")) and assert the same rejection string. The new test adds zero coverage and creates maintenance overhead — any change to the generic-error path must be kept in sync across two identical tests.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/dbt-tools/src/dbt-cli.ts
cb(null, "some unparseable output", "")
})

await expect(execDbtShow("SELECT 1")).rejects.toThrow("Could not parse dbt show output in any format")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Duplicate test: "preserves generic 'Could not parse' when dbt exited 0 but output unparseable" tests the exact same scenario as the existing "Tier 3: throws with helpful message when all tiers fail" test. Both use an identical mock (cb(null, "some unparseable output", "")) and assert the same rejection string. The new test adds zero coverage and creates maintenance overhead — any change to the generic-error path must be kept in sync across two identical tests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/dbt-tools/test/dbt-cli.test.ts, line 205:

<comment>Duplicate test: "preserves generic 'Could not parse' when dbt exited 0 but output unparseable" tests the exact same scenario as the existing "Tier 3: throws with helpful message when all tiers fail" test. Both use an identical mock (cb(null, "some unparseable output", "")) and assert the same rejection string. The new test adds zero coverage and creates maintenance overhead — any change to the generic-error path must be kept in sync across two identical tests.</comment>

<file context>
@@ -149,6 +149,73 @@ describe("execDbtShow", () => {
+      cb(null, "some unparseable output", "")
+    })
+
+    await expect(execDbtShow("SELECT 1")).rejects.toThrow("Could not parse dbt show output in any format")
+  })
+
</file context>

`execDbtShow` swallowed `run()` rejections silently — when `dbt show`
crashed (e.g. corrupted `dbt_packages/*`, missing `dbt_project.yml`,
DB connection refused), callers saw a misleading "Could not parse
dbt show output in any format" message and treated it as transient.
The real `Runtime Error: ...` from `dbt`'s stderr never surfaced.

Capture the `execFile` rejection's `.stderr` and `.stdout`, scan
recovered JSON log lines for `level: "error"` events (dbt with
`--log-format json` emits structured error events even on crash),
and surface the real error. Preserve the existing generic message
only when both `run()` invocations exit 0 but the output is genuinely
unparseable — the condition the message was actually designed for.

- src/dbt-cli.ts: 2 catch blocks now retain the error; new
  `extractDbtError()` helper picks structured event > stderr > message.
- test/dbt-cli.test.ts: 6 new cases (real stderr surfaces, structured
  event preferred, ENOENT fallback, generic message preserved on
  exit-0 unparseable). 24/24 pass.

Scoped to `execDbtShow`. `execDbtCompile` and `execDbtCompileInline`
share the same masking pattern but have manifest.json / `--quiet`
fallbacks that reduce impact — addressed separately if needed.

Closes #932
@sahrizvi sahrizvi force-pushed the fix/bubble-dbt-show-error-932 branch from 62e21bf to 064b0dd Compare June 11, 2026 20:46
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

2 similar comments
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@dev-punia-altimate

Copy link
Copy Markdown
Contributor

❌ Tests — Failures Detected

TypeScript — 15 failure(s)

  • connection_refused [1.00ms]
  • timeout
  • permission_denied
  • parse_error
  • network_error
  • auth_failure [1.00ms]
  • rate_limit
  • internal_error
  • empty_error
  • connection_refused
  • timeout
  • permission_denied
  • parse_error
  • network_error
  • auth_failure [1.00ms]

Next Step

Please address the failing cases above and re-run verification.

cc @sahrizvi

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

altimate-dbt execute masks real dbt show error with generic "Could not parse" message

2 participants