fix(dbt-tools): move to dbt-integration 0.3.17 so the dbt adapter starts under Bun
Issue for this PR
Closes #1367
Type of change
- Bug fix
What does this PR do?
Scope: No changes beyond what the 0.3.17 bump requires, plus reading the stale-manifest flag. What the bump requires includes dbt_lineage skipping the adapter start while dbt-integration has no getModelLineage.
Moves packages/dbt-tools from @altimateai/dbt-integration 0.2.14 to ^0.3.17. 0.2.x starts its Python child through python-bridge, whose bluebird.promisifyAll(child_process) throws under Bun (this.#stdin), so the dbt-first path of sql_execute never ran. 0.3.x vendors the bridge over a plain fd-3 pipe; inside a single-file executable it runs its script from a temporary directory that it removes on exit. 0.3.17 also pipes the bridge's stdout and stderr to the adapter's terminal, so dbt's log lines stay off the TUI, run --format json and mcp serve. python-bridge and bluebird leave the lockfile.
adapter.ts: 0.3.x's adapter takes aUnitTestParserand aSemanticModelParser; the Fusion integration no longer takes the cloud-variant detector. dbt Cloud keeps it and also gets the dependencies of the dbt Core command engine that every variant but fusion-stable runs on.adapter.ts/index.ts: 0.3.17 logs the bridge's stderr at debug, which the terminal here dropped. A crash at bridge startup is told only there, so those lines go in the log buffer, and a failed start returns them (logsin the JSON, stderr with--format text).diagnose()loses its python-bridge case, which 0.3.x can't reach.stale-manifest.ts,graph.ts,schema-verify.ts: after a failed project check, 0.3.17 can answer from the manifest an earlier parse left on disk and setsParsedManifest.stale.children,parentsandschema-verifythen lead with astale_manifestnote saying so, with its age;failure-text.tsputs it first in a--format texterror too. Answers on a current manifest don't change.dbt-lineage.ts/register.ts: 0.3.17 starts under Bun but has nogetModelLineage, so a call with write access would rundbt depsand a parse, wait up to 120 s, then answer from the manifest anyway. It now answers from the manifest at once. The check reads the installed class, so it opens by itself in a release that has the method.copy-python.ts/release.yml/publish.ts: the bridge findsnode_python_bridge.pyfrom the bundle's own location (fileURLToPath(import.meta.url)), so the__dirnamepatch is gone. The build and release smoke check fail if those references go or a build-time path appears.- Tests:
build-adversarial.test.tstested the removed patch; its other checks move tobuild-integrity.test.ts, plus a test that runs the package in thepublish.tslayout from another directory.adapter-bun.test.tsruns the CLI from source under Bun in core, corecommand and dbt Cloud modes and checks that each spawns the bridge, with a stand-inpythonthat prints a marker on each stream. The dbt Cloud cases get a stand-in dbt Cloud CLI, because 0.3.17 starts dbt Cloud mode only on adbtthat says it is that CLI.stale-manifest.test.tscovers the note.dbt-first-execution.test.tsno longer skips its adapter test on the python-bridge error. Thedbt_lineagetests stub the lineage check, and one checks the guard.
Claims
- C1: Under Bun, the dbt-first path of
sql_executestarts the dbt adapter and runs the query through dbt, from source and from the compiled binary. - C2: The compiled binary runs the bridge from a real directory, not
/$bunfs. No Python process and no temporary bridge directory is left after the call. - C3:
altimate-dbt, laid out aspublish.tsships it and run by Node from another directory, starts thenode_python_bridge.pynext to its own bundle. The build and the release smoke check fail if a build-time path is baked into the bundle. - C4:
adapter.tsbuilds the adapter with what 0.3.x's constructors take: both new parsers, the cloud-variant detector for dbt Cloud only, and dbt Cloud's Core command engine dependencies. - C5: No line the bridge prints is written to the host's stdout or stderr as it arrives. Its stderr lines go to
altimate-dbt's log buffer, which leaves only inside an error answer (C6); opencode never reads that buffer.mcp serve --workspace-toolswrites only protocol frames to stdout through asql_executecall. - C6: When the bridge fails at startup, what it printed on stderr reaches the CLI's caller: in
logsin the JSON answer, on stderr with--format text. - C7: After a failed project check, an answer from
children,parentsorschema-verifybuilt from a manifest an earlier parse left, its errors included, says so and gives the manifest's age, in the JSON answer and with--format text. On a current manifest those answers are unchanged. - C8: While dbt-integration has no
getModelLineage,dbt_lineagestarts no dbt adapter.sql_execute's dbt path doesn't change.
Residuals
- R1: When the adapter can't start (no dbt in the venv, a broken project),
sql_executestill falls back to the configured warehouse without saying why, as onmain. - R2: With a stale manifest,
childrenandparentsanswer{ stale_manifest, models }instead of a list, so the note leads a long answer. Nothing in this repo parses them; agents read them. - R3:
children's andparents'dbt lsfallback is a live answer, so it carries no note. - R4:
diagnose()inindex.tsdoesn't match 0.3.17's interpreter-not-found message and would give the default fix text. The CLI's prerequisite check reports a missing interpreter before that point. - R5: The note goes on every
schema-verifyanswer while the manifest is stale, sobuild's per-model summary would repeat it for each mismatch, and aschema-verifywith no--modelcarries it.buildwrites that summary only after a successful project-widedbt build.
How did you verify your code works?
This branch's columns were run on the published @altimateai/dbt-integration 0.3.17 from npm.
sql_execute through the tool registry (debug agent build --tool sql_execute, no model turn) in a dbt-core 1.11.7 / DuckDB project with no warehouse configured, select count(*) as n from {{ ref("customers") }}, which only dbt can run:
| before | this branch | |
|---|---|---|
| compiled binary | v0.12.8: falls back, "No warehouse configured" | n = 100 via dbt (core, corecommand) |
| from source (Bun) | main: falls back, same | n = 100 via dbt |
The binary left no Python process or temporary directory, and neither stream had a dbt log line.
| Behaviour | dbt-integration 0.3.15 | 0.3.17, without this PR's handling | this branch |
|---|---|---|---|
mcp serve --workspace-tools, non-frame stdout lines over a sql_execute call | 12 | 0 | |
| Traceback at bridge startup | on stderr | nowhere | in logs; on stderr with --format text |
children, parents with a broken profile and a 2-hour-old manifest (core, corecommand) | a list, exit 0, nothing about its age | leads with "…written 2 hours ago" | |
schema-verify on the same project | its error, nothing about the manifest's age | the same error, with the note | |
dbt_lineage with write access (jaffle, from source) | starts the adapter, 7.9–8.0 s, adds "dbt integration not used: dbt-integration has no getModelLineage" | no adapter, 4.1–5.0 s, no note |
altimate-dbtin thepublish.tslayout under Node 22 starts the bridge next to its own bundle.- dbt-tools: typecheck clean, 112 pass, 0 fail, the same with no
dbt, dbt-core or a dbt Cloud CLI onPATH, and with Node 22 onPATH. opencode: typecheck clean; its 28 dbt test files pass, 578 with nodbtonPATHand 583 with dbt-core, which runs the real-dbt validators.dbt-first-executionpasses 10 of 10 against a real project in core and corecommand. - Each new test fails with its fix removed: the stderr lines, the failed start's
logs, the listener order, the note on each command and in a text error, and thedbt_lineageguard.
Cost side effects
None. sql_execute still sends one query per call; in a dbt project it now runs through dbt's adapter instead of falling back to the configured warehouse. dbt_lineage starts no adapter, so it runs no dbt deps or parse.
Checklist
- I have tested my changes locally
- I have not included unrelated changes in this PR
- I checked for cost side effects (model calls, queries or cloud resources that grow with user data) and described them, or wrote "none"
Appendix: complexity delta
Appendix — complexity delta (dbt-integration 0.3.17 bump)
3aa43a81a3 → 88f730fe99 · only functions this diff touches · advisory, not a gate.
| Function | File | Cognitive | CCN | Status |
|---|---|---|---|---|
verify L35 | packages/dbt-tools/src/commands/schema-verify.ts | new → 26 | new → 30 | new ≥15 — needs decomposition |
fromAdapter L213 | packages/opencode/src/altimate/tools/dbt-lineage.ts | 17 → 18 ▲ | 13 → 14 ▲ | hard to follow |
main L114 | packages/dbt-tools/src/index.ts | 15 → 13 ▼ | 29 → 28 ▼ | ▼ improved — moderate |
output L68 | packages/dbt-tools/src/index.ts | 14 → 11 ▼ | 12 → 11 ▼ | ▼ improved — moderate |
diagnose L39 | packages/dbt-tools/src/index.ts | 5 → 4 ▼ | 12 → 9 ▼ | ▼ improved — trivial |
schemaVerify L29 | packages/dbt-tools/src/commands/schema-verify.ts | 26 → 1 ▼ | 30 → 2 ▼ | ▼ improved — needs decomposition → trivial |
Summary: 14 touched · 2 rose · 5 improved · 7 new (max cognitive 26) · net cognitive Δ +6
🎯 Where the weight sits (flagged functions):
verify(packages/dbt-tools/src/commands/schema-verify.ts): densest branching L85–95 (~12 branch points, nesting to depth 2; heuristic)
Disposition: for each flagged row that lands ≥15 — reduce it, or record the residual (Rn) that justifies the shape. One line each. Flagged rows under 15 are attention markers only.
ℹ️ How to read these numbers
Cognitive (Sonar spec) counts breaks in linear reading flow — each if/loop/catch/ternary/boolean-operator switch adds 1, and nesting makes every further break cost more. It approximates how much you must hold in your head to follow the function: 0–5 trivial · 6–10 easy · 11–15 moderate (15 = Sonar's recommended per-function cap) · 16–25 hard to follow · >25 needs decomposition.
CCN (cyclomatic) counts independent paths — also the minimum number of test cases for full branch coverage of the function.
Only functions this diff touches are measured, as deltas — pre-existing complexity is not counted against this change. Rising numbers aren't automatically wrong; they're where review attention should go. Test files excluded.
verify is schemaVerify's body, moved unchanged so every answer gets the note. fromAdapter's +1 is the guard for C8.
🤖 Generated with Claude Code
https://claude.ai/code/session_01VEmKQx8gR7vd6ettA3mWjP
Summary by CodeRabbit
- New Features
- Added support for parsing dbt unit tests and semantic models.
- Bug Fixes
- Improved the CLI’s ability to locate its Python bridge when launched from a different directory.
- Strengthened package checks to catch missing bridge files and unintended references to local build environments.
- Verified that installed bundles can start correctly and that repeated builds produce consistent output. These checks cover dbt Cloud and Core Command Engine configurations.