fix: bubble real dbt show error instead of generic "Could not parse"
PINEAPPLE
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
execFilerejection on both tiers (primaryRunError,plainRunError). - Recover any partial JSON log lines from the failed run's
stdout— dbt with--log-format jsonemits structuredlevel: "error"events before exit. - Before the generic throw, call
extractDbtError(...)which picks (in order): structured error event > primary stderr > plain stderr > exception message. - If
extractDbtErrorreturns anything, surface it asdbt 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 showexits 0 but emits unparseable output (regression guard preserved) - Real stderr from a crashing
dbt showsurfaces 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
- Issue: #932
- Catalog entry:
03-issues-and-fixes.mdIssue #14 - Discovered while investigating the auto-deps clobber on a sister repo (altimate-dbt-integration#120); the masked error happened to be a corrupted
dbt_packages/fixture
Summary by cubic
Bubble real dbt errors across execDbtShow, execDbtCompile, and execDbtCompileInline, while redacting inline SQL and separating parser failures from dbt execution issues. Fixes misleading “Could not parse” cases and makes failures actionable. Addresses #932; closes #943, #944, #945.
- Bug Fixes
run()now attachesstdout/stderronexecFilerejection; shared helpersextractDbtError/fallbackExitMessage/bubbleDbtErrorprefer last structured JSON error > primary stderr > plain stderr > a redacted exit-status, strip ANSI, and avoid doubling dbt category prefixes.execDbtShow: distinguish failure modes; if JSON-mode crashes, bubble the real dbt error; if JSON-mode succeeds but is unparseable and plain-text then fails, throw a parser error. Skip Tier 1/2 when the primary run failed, never feed crashed stdout into heuristics; still try Tier 3 and return the table if it succeeds.execDbtCompileandexecDbtCompileInline: adopt the same error bubbling and SQL redaction (covers exit codes and signal/timeouts); also bubble errors from manifest/plain fallbacks.- Guard data shape: treat truthy non-array
data.previewas empty rows to avoid malformed results. - Tests expanded for JSON/stderr preference, parser-vs-dbt attribution, ANSI strip, SQL redaction, Buffer I/O, Tier 3 recovery, and non-array preview; 41 passing tests.
Summary by CodeRabbit
-
Bug Fixes
- Improved error handling for
dbt showto surface actual dbt failure details, preferring structured JSON error events when available and avoiding generic “Could not parse …” messages on real crashes. - Made
dbt compileanddbt compile --inlinereport consistent, safer failure messages (ANSI cleaned and inline SQL not leaked).
- Improved error handling for
-
Tests
- Expanded coverage for
dbt show/compile failure modes, including fallback behavior when process output is missing, correct error-event selection, retry recovery, and message sanitization.
- Expanded coverage for