Skip to content

fix: thread configured busyTimeoutMs through remaining read-only query call sites#1883

Closed
carlos-alm wants to merge 2 commits into
fix/issue-1762-titan-grind-dead-symbol-shapefrom
fix/issue-1763-busytimeout-readonly-wiring
Closed

fix: thread configured busyTimeoutMs through remaining read-only query call sites#1883
carlos-alm wants to merge 2 commits into
fix/issue-1762-titan-grind-dead-symbol-shapefrom
fix/issue-1763-busytimeout-readonly-wiring

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • Re-grepped and found the call-site list had grown to ~32 (not ~15 as originally filed, given how much this batch has touched these files). Categorized and fixed all of them:
    • Already loaded config before opening the DB → threaded through directly (2 sites).
    • Loaded config only after opening the DB → reordered, verified safe (loadConfig() only throws for one narrow case — non-string llm.apiKeyCommand — and no existing test pins error ordering for any of these sites; mirrors the precedent already established by resolveDbSettings()).
    • Never loaded config at all → added a new resolveBusyTimeoutMs(customDbPath?) helper call, including query-helpers.ts's shared withReadonlyDb() which transitively fixes exports.ts/dependencies.ts/context.ts for free.
  • The Rust native DB layer gap (hardcoded PRAGMA busy_timeout = 5000, no config awareness) is genuinely out of scope per the issue's own framing — investigated briefly, doesn't need config-loading capability (JS already resolves the value) but does need a new FFI parameter across 6 call sites + a napi rebuild. Filed as Thread config.db.busyTimeoutMs into the Rust native DB layer (NativeDatabase::open_readonly/open_read_write) #1882 rather than attempted here. Using Refs #1763 rather than Closes since this issue named both the TS wiring and the Rust layer as its scope — only the former is done.

Refs #1763

Test plan

  • npm test — full suite green (3528 passed, 0 failed) — caught and fixed a real bug during the gate: 26 tests in complexity.test.ts mock loadConfig() to return {}, and non-optional-chained config.db.busyTimeoutMs crashed on the missing key; fixed with config.db?.busyTimeoutMs matching the existing defensive pattern elsewhere
  • npm run lint / npx tsc --noEmit / npm run build — clean
  • New tests/unit/busy-timeout-query-sites.test.ts (7 tests) spies on Database.prototype.pragma to assert the configured value is actually applied
  • Real end-to-end CLI verification: scratch project with .codegraphrc.json (db.busyTimeoutMs: 424242), ran 16 different commands, confirmed every one used the configured value; confirmed default (5000) still applies with no override

Filed #1881 for an out-of-scope finding: several functions resolve config via process.cwd() rather than the resolved --db path (pre-existing, not introduced here — preserved each function's existing resolution mechanism). Filed #1882 for the Rust layer.

Stacked on #1880 (base branch fix/issue-1762-titan-grind-dead-symbol-shape) — only this diff is this issue's change.

…y call sites

Extends the config-driven db.busyTimeoutMs wiring (started in #1748/#1749 for
openDb()/openReadonlyOrFail() and the call sites that already held a loaded
config) to the ~30 ad-hoc read-only query call sites across features/*,
domain/analysis/*, and domain/search/* that previously called
openReadonlyOrFail() directly and silently fell back to DEFAULTS.db.busyTimeoutMs.

Adds resolveBusyTimeoutMs() to src/db/connection.ts (exported via db/index.ts),
sharing rootDir derivation with resolveDbSettings() via a new
deriveRootDirFromDbPath() helper. For call sites that never loaded config in
their path, this new call is threaded in directly. Fixing withReadonlyDb() in
domain/analysis/query-helpers.ts centrally also covers its callers
(exports.ts, dependencies.ts, context.ts) for free.

For call sites that already loaded config but did so AFTER opening the DB
(diffImpactData, checkData, complexityData, manifestoData,
moduleBoundariesData), reordered config-load-before-db-open, mirroring the
precedent already established by resolveDbSettings()/openReadonlyWithNative()
(see the phase-15 gauntlet handle-leak fix). Verified this reorder is safe:
loadConfig() only throws ConfigError for one narrow case (a non-string
llm.apiKeyCommand), unrelated to db.busyTimeoutMs and independent of whether
the DB exists — no existing test pins the previous error ordering for any of
these call sites. Made every new config.db.busyTimeoutMs access
optional-chained (config.db?.busyTimeoutMs) after this surfaced a real crash
in tests/integration/complexity.test.ts, which mocks loadConfig() to return a
partial object without a db key.

Verified end-to-end against a real built graph with a custom .codegraphrc.json
(db.busyTimeoutMs override) via the CLI (owners, check, structure --modules,
map, audit, complexity, dataflow, search, co-change, ast, flow, cfg, roles,
where, children), confirming the configured value reaches the PRAGMA
busy_timeout call and the default still applies with no config override.
Added tests/unit/busy-timeout-query-sites.test.ts covering resolveBusyTimeoutMs
directly plus a representative sample of call sites from each category
(ownersData, cfgData, manifestoData, hybridSearchData, withReadonlyDb),
spying on Database.prototype.pragma to assert the configured value is
actually applied.

Filed two follow-ups discovered while completing this wiring pass, both
intentionally out of scope here:
- #1881: several functions (manifestoData, hybridSearchData,
  moduleBoundariesData, diffImpactData, complexityData, auditData) resolve
  their config via process.cwd() rather than the resolved --db path, unlike
  resolveDbSettings()/checkData() — a pre-existing inconsistency, not
  introduced by this change, that affects cross-repo --db usage for several
  config fields (not just busyTimeoutMs).
- #1882: the Rust native DB layer (NativeDatabase::open_readonly/
  open_read_write in crates/codegraph-core/src/db/connection.rs) still
  hardcodes PRAGMA busy_timeout = 5000. Investigated and confirmed this does
  NOT require giving Rust config-loading capability (the JS side already
  resolves the value) — it requires threading an already-resolved number
  across the napi FFI boundary plus a native rebuild across platforms, which
  is a heavier-weight change than this wiring pass.

Refs #1763 (TS read-only call-site wiring is complete; the Rust portion of
that issue's scope is deferred to #1882, so this does not close it).

docs check acknowledged: internal config wiring only, no new config keys or
CLI flags — db.busyTimeoutMs was already documented by the original phase-7
commit that introduced it.

Impact: 36 functions changed, 131 affected
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR threads the user-configured db.busyTimeoutMs value through ~32 read-only query call sites across features/, domain/analysis/, and domain/search/, replacing the hardcoded default-only path. Two new helpers are introduced — deriveRootDirFromDbPath (private) and the exported resolveBusyTimeoutMs/resolveDbConfig — and withReadonlyDb is updated to resolve config once and pass it to its callback, avoiding a redundant loadConfig call in callers that already use resolveAnalysisOpts.

  • New helpers in db/connection.ts: resolveBusyTimeoutMs and resolveDbConfig provide consistent, DB-path-rooted config resolution; deriveRootDirFromDbPath centralises the rootDir derivation to prevent drift between resolveDbSettings and the new helpers.
  • withReadonlyDb signature expanded: callback now receives (db, config), letting callers like exports.ts, context.ts, and exports.ts reuse the already-loaded config for resolveAnalysisOpts instead of triggering a second loadConfig.
  • New regression test (tests/unit/busy-timeout-query-sites.test.ts) uses Database.prototype.pragma spy to assert the configured value reaches the SQLite pragma for a representative sample of all three fix categories.

Confidence Score: 5/5

Safe to merge — the change is a mechanical, well-tested threading of a config value through ~32 call sites with no logic alterations beyond call ordering.

All production openReadonlyOrFail call sites now receive a config-derived timeout. The new helpers are thin wrappers around the existing deriveRootDirFromDbPath/loadConfig stack already proven in resolveDbSettings. The withReadonlyDb signature extension is backward-compatible, and the 7 new regression tests verify the pragma reaches SQLite for each fix category. The full suite (3528 tests) is green.

No files require special attention.

Important Files Changed

Filename Overview
src/db/connection.ts Adds deriveRootDirFromDbPath, resolveDbConfig, and resolveBusyTimeoutMs; refactors resolveDbSettings to use the shared helper. Logic is clean and well-documented.
src/domain/analysis/query-helpers.ts Expands withReadonlyDb to resolve config before opening the DB and pass it to the callback; backward-compatible since TypeScript allows callbacks with fewer parameters.
src/features/complexity-query.ts Moves resolveComplexityQueryOptions call before openReadonlyOrFail, correctly hoisting busyTimeoutMs out of the resolved options.
src/features/check.ts Reorders findDbPath/loadConfig before openReadonlyOrFail; safe because findDbPath does not require an open handle.
src/domain/analysis/context.ts Two withReadonlyDb callbacks updated to accept (db, config) and pass the resolved config to resolveAnalysisOpts via opts.config ?? config.
tests/unit/busy-timeout-query-sites.test.ts New 7-test regression suite; uses a Database.prototype.pragma spy to verify the configured value reaches SQLite across all three fix categories and the shared withReadonlyDb helper.
src/db/index.ts Re-exports the two new public helpers resolveBusyTimeoutMs and resolveDbConfig.
src/features/audit.ts DB opened after config resolution; config comes from loadConfig() (cwd-rooted) — pre-existing resolution mechanism intentionally preserved.
src/features/manifesto.ts Config load reordered before openReadonlyOrFail; uses loadConfig(process.cwd()) matching the pre-existing cwd-based resolution.

Reviews (3): Last reviewed commit: "fix: resolve config once in withReadonly..." | Re-trigger Greptile

Comment thread src/domain/analysis/query-helpers.ts Outdated
fn: (db: BetterSqlite3Database) => T,
): T {
const db = openReadonlyOrFail(customDbPath);
const db = openReadonlyOrFail(customDbPath, resolveBusyTimeoutMs(customDbPath));

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.

P2 Double loadConfig for callers that also use resolveAnalysisOpts

withReadonlyDb now calls resolveBusyTimeoutMs(customDbPath)loadConfig(deriveRootDirFromDbPath(customDbPath)). Callers such as exports.ts, dependencies.ts, and context.ts also call resolveAnalysisOpts inside their callback, which invokes loadConfig() (cwd-rooted) when opts.config is absent. This results in two loadConfig calls per query with different root directories — one DB-path-derived (correct for the timeout) and one cwd-derived (pre-existing behaviour for other settings).

The timeout will be set correctly, but if loadConfig is not cached and involves disk I/O, the extra call adds overhead on every withReadonlyDb-backed query. A simple mitigation would be to expose an overload that accepts an already-resolved busyTimeoutMs when the caller already holds a config, or to cache loadConfig results by rootDir.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

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.

Fixed — added an exported resolveDbConfig() in connection.ts (shared by resolveBusyTimeoutMs), and withReadonlyDb now resolves it once and passes it to the callback. exports.ts/context.ts pass that resolved config into resolveAnalysisOpts as opts.config ?? config, eliminating the second loadConfig() call. Filed #1941 for the same pattern in withRepo/fn-impact.ts — out of scope here since openRepo/resolveDbSettings are shared by every native-engine call site, not just read-only queries.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

40 functions changed132 callers affected across 84 files

  • resolveStickyModel in src/cli/commands/embed.ts:14 (1 transitive callers)
  • openGraph in src/cli/shared/open-graph.ts:7 (3 transitive callers)
  • deriveRootDirFromDbPath in src/db/connection.ts:396 (42 transitive callers)
  • resolveDbSettings in src/db/connection.ts:411 (24 transitive callers)
  • resolveDbConfig in src/db/connection.ts:435 (65 transitive callers)
  • resolveBusyTimeoutMs in src/db/connection.ts:445 (73 transitive callers)
  • contextData in src/domain/analysis/context.ts:426 (2 transitive callers)
  • explainData in src/domain/analysis/context.ts:505 (5 transitive callers)
  • diffImpactData in src/domain/analysis/diff-impact.ts:259 (3 transitive callers)
  • exportsData in src/domain/analysis/exports.ts:24 (2 transitive callers)
  • moduleMapData in src/domain/analysis/module-map.ts:314 (2 transitive callers)
  • withReadonlyDb in src/domain/analysis/query-helpers.ts:14 (18 transitive callers)
  • dynamicCallsData in src/domain/analysis/roles.ts:15 (2 transitive callers)
  • rolesData in src/domain/analysis/roles.ts:32 (2 transitive callers)
  • queryNameData in src/domain/analysis/symbol-lookup.ts:94 (1 transitive callers)
  • whereData in src/domain/analysis/symbol-lookup.ts:194 (2 transitive callers)
  • listFunctionsData in src/domain/analysis/symbol-lookup.ts:213 (0 transitive callers)
  • childrenData in src/domain/analysis/symbol-lookup.ts:240 (2 transitive callers)
  • hybridSearchData in src/domain/search/search/hybrid.ts:176 (3 transitive callers)
  • ftsSearchData in src/domain/search/search/keyword.ts:37 (6 transitive callers)

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm deleted the branch fix/issue-1762-titan-grind-dead-symbol-shape July 7, 2026 02:23
@carlos-alm carlos-alm closed this Jul 7, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 7, 2026
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Recreated as #1943 after this PR was closed by the squash+delete-branch merge of its stacked base #1942.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant