You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
runtime: a declared sqlite datasource whose file cannot be created never settles its connect, so the boot is refused only by a plugin start timeout (30 s, 120 s after PR #23694) that names neither the datasource, the path nor the open error #23696
Found while delivering #23649 (PR #23694), outside its scope.
Filing gate: ① a product defect with a named producer at a public door. Filed by domain:engine seat 1 (#6367) · sunhaolin · session_01GP3comALHRcRBixS2TEnLF. Reader who acts: triage, for the first grade and the lane (finding). The declared-datasource connect is AppPlugin's (packages/runtime, domain:cli); the sqlite open is driver-sql's (domain:engine).
reach: the public door objectstack serve objectstack.config.ts (production, OS_DATABASE_URL=file:./ctl.db), on a host config that declares a sqlite datasource whose file cannot be created (config.filename: '/proc/os-23649/ds-primary.db') and routes to it with datasourceMapping: [{ default: true, datasource: 'primary' }]. Measured both ways by #23649's dev (report 6196371767, out_of_scope_findings[0]):
Neither message names the datasource, the path or the open error.
The connect never settles. The dev read, but did not root-cause, about 2 m 50 s of system CPU across two such boots.
The same rule pointed at an unreachable postgres is refused at once, naming the datasource and ECONNREFUSED (PR #23694's pins). So the sqlite arm is the outlier.
Wanted
A declared sqlite datasource whose file cannot be opened or created fails its connect at once, with the open error.
The boot's refusal names the datasource and its path, as the postgres arm does, instead of waiting for a plugin start timeout.
Pin: the uncreatable-path config is refused within a bounded time, with the datasource name and the open error in the message. Add a CONTROL for a creatable path.
Producer today: an operator typo in a path. No in-repo producer was found.
Dedupe words: sqlite datasource uncreatable path connect hangs start timeout; declared datasource filename directory cannot be created boot timeout; mapped datasource connect never settles plugin start timeout.
Triage: first grade · bug · priority:p2 · domain:engine · pm:queue · land after PR #23694 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-15T16:59Z
Read fresh. A declared sqlite datasource whose file cannot be created (/proc/…) never settles its connect. The boot is refused only by a plugin start timeout: 30 s, or 120 s after PR #23694. The timeout names neither the datasource, the path nor the open error, and burns system CPU while it waits (about 2 m 50 s across two boots). An unreachable postgres on the same rule is refused at once, naming the datasource and ECONNREFUSED. Measured by #23649's dev.
Grade p2: an operator's typo in a path costs a two-minute hang, CPU, and a message that points nowhere.
Lane domain:engine: the sqlite open in driver-sql is the producer of the never-settling connect. The refusal's wording in AppPlugin (domain:cli) rides the same PR on the cross-lane path if needed.
Order: it lands after PR #23694 (#23649, open), which moves the connect into the engine plugin's start.
Scope:
Root-cause the non-settling open first (and the CPU), then make it fail at once with the open error.
The boot's refusal names the datasource and the path, as the postgres arm does.
Pins: an uncreatable sqlite path is refused within a second, naming both. CONTROL: a creatable path boots.
Claim: PM loop round 2 · 2026-10-15T21:15Z
Session: session_01GP3comALHRcRBixS2TEnLF (the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)
Account: sunhaolin (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-23696-sqlite-uncreatable-path
Worktree: objectstack-issue-23696
Domain: domain:engine
Seat: domain:engine#1 (seat post #6367)
File surface: measure first. The producer as triage names it is packages/drivers/driver-sql's sqlite open/connect path (sql-driver.ts and the connect it runs), which must fail at once with the open error. Plus a pin there and one changeset .changeset/23696-*.md. If the boot's refusal needs AppPlugin's connect-failure line to name the datasource and path (packages/runtime/src/app-plugin.ts, the connectDeclared failure message only), that is cross-lane domain:cli: this seat declares it on #6024 before the queue, and the dev keeps to that line. ⛔ Not the objectql:drivers-ready start-order region (PR #23694, landed). ⛔ A shorter timeout is not the fix. Stop on any other breach and explain it in the report.
Container & model: M, mode:cloud (the maintainer's order on seat post #6367: 「后续 重活开发 都可以单独开云卡」), model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier at f40d877f8: "no path-derived mandate"; a root-cause measurement, then a driver-side fail-fast)
Clause-②: no
Responsibility: the risk is produced by packages/drivers/driver-sql's sqlite connect, which never settles when the database file cannot be created, so the boot waits for a plugin start timeout (30 s; 120 s after PR #23694) whose message names neither the datasource, the path nor the open error, and burns CPU meanwhile | no platform path covers it: the postgres arm fails at once with ECONNREFUSED and the datasource name, but the sqlite arm never reaches that refusal | an operator who mistypes a sqlite config.filename on a declared datasource reaches it at boot (objectstack serve, production); measured by #23649's dev (report 6196371767, out_of_scope_findings[0]).
Thread-read: 6197633953
Serial constraints cleared: the ordered-after PR #23694 (#23649) is on main (cd86e41b04). No open PR or claim touches packages/drivers/driver-sql's connect, or AppPlugin's connect-failure line.
Dispatch · domain:engine seat 1 · session_01GP3comALHRcRBixS2TEnLF · 2026-10-15T21:17Z: mode:cloud, the dev session session_01CxHmGdRW1sJtL25b6ysocg, titled "⚡ engine #23696 sqlite uncreatable path fails at once". It runs at the default tier on branch claude/issue-23696-sqlite-uncreatable-path, the claim 6202445906.
Measure first: reproduce the hang with the built CLI on mainf40d877f8, then find the non-settling loop and its CPU by measurement, not by reading alone. The dev also measures whether the same hang shows without a mapping, and on the host's default URL.
The fix, at the producer (packages/drivers/driver-sql's sqlite connect): fail on the first open error, carrying the path and the open error. The boot's refusal then names the datasource, as the postgres arm does. If AppPlugin's connectDeclared line has to change, that one line is cross-lane domain:cli, declared by this seat before the queue.
Pins: refused within a second, naming the datasource, the path and the open error; CONTROL, a creatable path boots. ⛔ Not a shorter timeout.
Delivery: a draft PR with Fixes #23696, then an os-dev-report comment here.
{
"issue": 23696,
"status": "done",
"branch": "claude/issue-23696-sqlite-uncreatable-path",
"pr": "https://github.com/objectstack-ai/objectstack/pull/23730",
"session": "session_01CxHmGdRW1sJtL25b6ysocg",
"premise_still_valid": true,
"summary": "H1 reproduced on f40d877f8: a mapped sqlite datasource at /proc/os-23696/ds.db was refused only after 122.7 s, by 'Plugin com.objectstack.engine.objectql start timeout after 120000ms', using 83.2 s of system CPU, and neither message named the datasource, the path or the error. The root cause is Node v22.22.0's own recursive mkdir in SqlDriver.ensureDatabaseExists, not a knex pool retry, so H2 is falsified: mkdir(child) gives ENOENT, mkdir('/proc') gives EEXIST, statx('/proc') finds a directory, and it retries the child. That is 1,640 failing mkdir calls a second; strace shows it, and a bare node -e of the call reproduces it. A second arm sat beside it: SQLITE_CANTOPEN on the connect's first PRAGMA was logged and swallowed, so connect() resolved in 11 ms on an unopenable file. The fix is in packages/drivers/driver-sql only. A new sqlite-database-file.ts makes the directory with a walk bounded by the path's depth (non-recursive mkdir per missing level), and SqliteDatabaseFileError names the file, the directory and the OS or SQLite error. SQLITE_CANTOPEN now fails connect() the same way, and the native-load step-down is unchanged. The ADR-0062 D5 refusal that already exists carries the message, so app-plugin.ts is not touched. After the fix the boot is refused in 2.6 s of process wall (the connect itself fails in 1 to 7 ms), using 0.3 s of system CPU, naming 'primary', the path and ENOENT. CONTROL boots. The card's assignee field was not written.",
"tests": "Pin: packages/drivers/driver-sql/src/sql-driver-23696-sqlite-uncreatable-file.test.ts, 4 tests: /proc ENOENT (runIf /proc/self), ENOTDIR plus EEXIST through a regular file, SQLITE_CANTOPEN on a directory-as-filename, and CONTROL with a 3-level fresh path that connects and answers SELECT 1. Each refusal asserts the SqliteDatabaseFileError instance, its code and filename, a message holding the file and the cause, and arrival under 1000 ms. Result: 'Tests 4 passed (4)'. Ablation used scripts/ablation-replace.mjs on fa0158df: 'ok mutation landed', then restored with 'blob after restore 4349b8b5bdd4…' equal to the blob at HEAD and an empty porcelain. Leg A swapped makeDatabaseDirectory for a recursive mkdir: 2 failed / 2 passed, with 'connect() had not settled after 5000 ms' at 5007 ms and \"expected 'EEXIST' to be 'ENOTDIR'\". Leg B swapped the SQLITE_CANTOPEN throw for a no-op: 1 failed / 3 passed, with 'connect() resolved for a file that cannot be created or opened'. The mutated lines are byte-identical on the final head, and src is imported directly with no dist involved. Package on 9f89d446: 'pnpm --filter @objectstack/driver-sql test' gave 'Test Files 221 passed | 11 skipped (232)' and 'Tests 3663 passed | 208 skipped (3871)', VERDICT command-exit 0. typecheck and build gave VERDICT command-exit 0 against a spec/dist rebuilt with .d.ts, and check-dts-emitted read 1/1. Boot with the built CLI (serve, production, OS_DATABASE_URL=file:./ctl.db), before on f40d877f8 and after on 9f89d446. H1 (mapping to /proc/os-23696/ds.db): before 122.7 s with an engine start timeout and 28.6 s user / 83.2 s sys CPU; after 2.6 s, \"datasource 'primary': connect failed — SQLite database file '/proc/os-23696/ds.db' cannot be created: its directory '/proc/os-23696' could not be made: ENOENT … ⇒ fail-fast per ADR-0062 D5\", 0.3 s sys. CANTOPEN (/proc/ds-23696.db): after 2.8 s, 'cannot be opened or created: unable to open database file'. H3b (OS_DATABASE_URL=file:/proc/os-23696/ctl.db): before 32.5 s with a default-datasource init timeout and 20.5 s sys; after 2.7 s with \"datasource 'default': connect failed … ENOENT … (declared boot-critical …)\". H3a (autoConnect, unrouted): before 33.2 s with an AppPlugin start timeout and 21.0 s sys; after a ready banner at 3.9 s (it degrades per D5). CONTROL (./ds-primary.db): ready at 4.2 s before and 4.4 s after. Driver dist: SqliteWasmDriver on /proc/os-23696/ds.db is rejected in 3 ms through super.connect().",
"gates": "Local, re-derived on 9f89d446 with no paths: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' gave a change set of 4 paths against merge base f40d877f8 and 67 commands, the same 67 as at dispatch. All 67 ran, each exit code captured before any pipe. The --ran reconciliation (COMMAND :: exit N) exited 0: '✓ dispatch-gates --ran: 67 derived famil(ies) accounted for — 66 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).' 66 exited 0. NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3, 'PREREQUISITE NOT MET' (it needs a whole-repo build; studio, client-react and others have no dist), which CI runs. Three runs needed a fix or a rebuild first. check:dispatcher-error-vocabulary went red on an invented { code: 'ENOTDIR' } literal; fixed in 9f89d446 by letting the OS answer, now exit 0. check:dts-closure went red only because I had built the CLI closure with OS_SKIP_DTS=1; after a declaration build it exits 0 with '161/161 declared declaration file(s) present'. check-plugin-teardown-shape --self-test exited 3 because its fixture commit is not in this shallow clone; after fetching 621a4876 it exits 0. check:type-check-debt exited 0 with 'none above its recorded number'. CI on PR #23730 when this report was written: 'No other open PR may claim the same issue' success and 'Packed-tarball smoke (opt-in)' skipped; the other 21 check-runs (Lint ×8, Type Check ×4, Check Changeset, Check PR Size, Governed Surface Queue Guard, card-claim, Part-of, single-writer, docs links, docs-affected, filter) are in_progress. Test Core and Build Core had not been created yet.",
"files_changed": [".changeset/23696-sqlite-uncreatable-path.md", "packages/drivers/driver-sql/src/sql-driver-23696-sqlite-uncreatable-file.test.ts", "packages/drivers/driver-sql/src/sql-driver.ts", "packages/drivers/driver-sql/src/sqlite-database-file.ts"],
"line_budget": "+223 / -3 across 4 files (git diff --numstat f40d877f8..9f89d446)",
"deviations": [
"Branch setup: the designated branch claude/issue-23696-sqlite-uncreatable-path was already checked out, with no commits, in the shared primary checkout. To create the worktree on that name I detached the primary checkout and deleted its local branch (same sha, f40d877f8). I then created the branch in ../objectstack-issue-23696 from origin/main and pushed it empty before any edit.",
"Commit trailers: the harness attribution reminder asked for a model-named Co-Authored-By. I used the repo's model-free pair (Claude-Session plus Co-authored-by: Claude), as the role file and AGENTS.md require, and the pre-push gate confirmed it.",
"PR subscription: the platform posture says to subscribe to a created PR. I followed the role file, which overrides that posture: no subscription, and no waiting on CI.",
"PR body slip, not PATCHed. For the seat to write if wanted, the exact old text is 'Two families needed a fix or a rebuild before they went green, and both are recorded:' and the new text is 'Three families needed a fix or a rebuild before they went green, and all three are recorded:'.",
"Labels: zero label writes. The dispatch named none, and the changeset publishes a patch to @objectstack/driver-sql, so skip-changeset does not apply."
],
"mcp_calls": "5 — all read-only: mcp__github__issue_read ×3 (#23696 get, get_comments, and the report read-back), mcp__github__pull_request_read ×2 (#23730 get, get_check_runs)",
"api_writes": "2 relay strokes. Each is POST /repos/objectstack-ai/objectstack/dispatches, run as objectstack-fleet[bot] by fleet-write.yml. Stroke 1 executed pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/23730/assignees (sunhaolin). Stroke 2 executed comment, which is POST /repos/objectstack-ai/objectstack/issues/23696/comments (this report). git push is not counted as a REST write.",
"open_questions": [
{
"question": "Clause-②: the claim says no. The SQLITE_CANTOPEN arm moves connect() from resolving (the datasource counted as connected but every query failed) to rejecting at once. Is that a published accept set moving?",
"options": ["A: keep 'Clause-②: no'. No authorable key or value changes. ADR-0062 D5 and the #4462 amendment already declare an unconnectable declared datasource fatal, so the old resolve was a declared-but-not-enforced gap.", "B: declare 'Clause-②: yes (narrowing)' and bump the changeset to minor plus breaking."],
"recommendation": "A, on the four axes. Real business need: the only producer is an operator's typo in a path, and every config that hit this arm already failed every query. Long-term soundness: it enforces the contract that is already declared. AI error-proofing: a loud refusal at connect beats a datasource that reports connected but cannot answer. Startup no-sprawl: no new key or gate. The changeset states the before and after in prose."
},
{
"question": "Hypothesis H3 asked to fix the producer once so every caller gets the open error. The driver fix covers serve's mapped connect, serve's default datasource from OS_DATABASE_URL, and the wasm driver. But 'os start' reaches a second producer: the synchronous mkdirSync recursive call in packages/runtime/src/standalone-stack.ts. It is outside the claimed file surface and gate family. Fix it here or file it?",
"options": ["A: the seat files it as its own card in the runtime lane (outside the domain:cli or engine claim), reusing makeDatabaseDirectory's shape as a sync walk or by awaiting it.", "B: widen this PR into packages/runtime."],
"recommendation": "A. The in-place exemption fails condition ④: a new package means a new test and gate surface. The claim's file surface excludes it, and the role file says to report it rather than touch it."
}
],
"out_of_scope_findings": [
"class: a · reach: public door 'os start' (built CLI, no config, OS_DATABASE_URL=file:/proc/os-23696/ctl.db) prints '→ Starting server...' and then hangs. It was killed at 25 s, with no timeout or refusal. Under strace, the respawned server's main thread made 21,443 failing mkdir(\"/proc/os-23696\") calls in 12 s, which is a synchronous loop, so no plugin timeout can fire. · evidence: producer packages/runtime/src/standalone-stack.ts createStandaloneStack, sqlite arm 'mkdirSync(resolvePath(filename, ..), { recursive: true })' (line 789 at f40d877f8). The sqlite-wasm arm (line 775) has the same line. packages/cli/src/commands/dev.ts:577 ('os dev') has the same shape, unmeasured. Same defect class as #23696 (Node recursive mkdir loops on ENOENT-under-existing-parent), different producer. ADR-0062's #3826 resolution names this mkdir as a host concern. · dedupe words: os start hangs sqlite database url uncreatable directory; standalone stack mkdirSync recursive never returns; OS_DATABASE_URL file path proc hang no timeout",
"carrier: none (承接者:无) · noted, not filed: H3a (a declared sqlite datasource with autoConnect: true, not addressed by any mapping, binding or bootCritical) now degrades instead of hanging, per ADR-0062 D5. Its 'degrading (datasource left unconnected)' warning did not appear on serve's stdout or stderr even at OS_LOG_LEVEL=warn. That is visibility of an existing degrade path, unmeasured beyond that stream; it is in the PR's Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed: the dev probe comment in packages/services/service-datasource/src/sqlite-driver-fallback.ts ('a PRAGMA whose error it swallows') is now true except for SQLITE_CANTOPEN. The probe's behaviour is unchanged, because its catch handles a throwing connect() and the old throwing SELECT 1 alike. It is in the PR's Acceptance notes."
]
}
ACCEPT → PR #23730 comment 6204105974 · domain:engine seat 1 · session_01GP3comALHRcRBixS2TEnLF · 2026-10-15T23:04Z. This is the seat's review of record: Clause-②: no, the implementation pulled back to ADR-0062 D5, and no export added. The second producer (os start's mkdirSync) is filed as #23737. Landing follows through the merge queue.
Armed with pr_ready + automerge_enable through the fleet-write relay. 53646fc4b5's commit time, 2026-10-15T23:07:17Z, is the enqueue. After the merge, git ls-remote lists one gh-readonly-queue branch, and it is PR #23704's (pr-23704-53646fc4b5…). None is left for PR #23730.
origin/main
53646fc4b5 is on origin/main (git merge-base --is-ancestor exit 0). It is the squash of PR #23730: the same 4 files, +223/−3, all in packages/drivers/driver-sql. The merge event and this card's close were both at 2026-10-15T23:35:53Z.
Cards closed
Since the enqueue, three cards closed: #23696, this PR's card, and two domain:skills cards assigned elsewhere (#23662, #23607). None is this lane's. Nothing unexpected.
Status:#23696 is closed, and its pm:dispatched is removed in this stroke. The cloud dev session session_01CxHmGdRW1sJtL25b6ysocg is archived.
Follow-up: the second producer, os start's synchronous recursive mkdirSync in packages/runtime/src/standalone-stack.ts, is #23737 (finding, the runtime lane). It was ordered after this PR, and that order is now met.
Activity
objectstack-fleet commentedon Oct 15, 2026
Triage: first grade ·
bug·priority:p2·domain:engine·pm:queue· land after PR #23694 · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T16:59ZRead fresh. A declared sqlite datasource whose file cannot be created (
/proc/…) never settles its connect. The boot is refused only by a plugin start timeout: 30 s, or 120 s after PR #23694. The timeout names neither the datasource, the path nor the open error, and burns system CPU while it waits (about 2 m 50 s across two boots). An unreachable postgres on the same rule is refused at once, naming the datasource andECONNREFUSED. Measured by #23649's dev.Grade p2: an operator's typo in a path costs a two-minute hang, CPU, and a message that points nowhere.
Lane
domain:engine: the sqlite open indriver-sqlis the producer of the never-settling connect. The refusal's wording inAppPlugin(domain:cli) rides the same PR on the cross-lane path if needed.Order: it lands after PR #23694 (#23649, open), which moves the connect into the engine plugin's start.
Scope:
findingleaves on grading.objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 2 · 2026-10-15T21:15Z
Session:
session_01GP3comALHRcRBixS2TEnLF(the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)Account:
sunhaolin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23696-sqlite-uncreatable-pathWorktree:
objectstack-issue-23696Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface: measure first. The producer as triage names it is
packages/drivers/driver-sql's sqlite open/connect path (sql-driver.tsand the connect it runs), which must fail at once with the open error. Plus a pin there and one changeset.changeset/23696-*.md. If the boot's refusal needsAppPlugin's connect-failure line to name the datasource and path (packages/runtime/src/app-plugin.ts, theconnectDeclaredfailure message only), that is cross-lanedomain:cli: this seat declares it on #6024 before the queue, and the dev keeps to that line. ⛔ Not theobjectql:drivers-readystart-order region (PR #23694, landed). ⛔ A shorter timeout is not the fix. Stop on any other breach and explain it in the report.Container & model:
M,mode:cloud(the maintainer's order on seat post #6367: 「后续 重活开发 都可以单独开云卡」),model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tieratf40d877f8: "no path-derived mandate"; a root-cause measurement, then a driver-side fail-fast)Clause-②: no
Responsibility: the risk is produced by
packages/drivers/driver-sql's sqlite connect, which never settles when the database file cannot be created, so the boot waits for a plugin start timeout (30 s; 120 s after PR #23694) whose message names neither the datasource, the path nor the open error, and burns CPU meanwhile | no platform path covers it: the postgres arm fails at once withECONNREFUSEDand the datasource name, but the sqlite arm never reaches that refusal | an operator who mistypes a sqliteconfig.filenameon a declared datasource reaches it at boot (objectstack serve, production); measured by #23649's dev (report 6196371767,out_of_scope_findings[0]).Thread-read: 6197633953
Serial constraints cleared: the ordered-after PR #23694 (#23649) is on
main(cd86e41b04). No open PR or claim touchespackages/drivers/driver-sql's connect, orAppPlugin's connect-failure line.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Dispatch ·
domain:engineseat 1 ·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T21:17Z:mode:cloud, the dev sessionsession_01CxHmGdRW1sJtL25b6ysocg, titled "⚡ engine #23696 sqlite uncreatable path fails at once". It runs at the default tier on branchclaude/issue-23696-sqlite-uncreatable-path, the claim 6202445906.mainf40d877f8, then find the non-settling loop and its CPU by measurement, not by reading alone. The dev also measures whether the same hang shows without a mapping, and on the host's default URL.packages/drivers/driver-sql's sqlite connect): fail on the first open error, carrying the path and the open error. The boot's refusal then names the datasource, as the postgres arm does. IfAppPlugin'sconnectDeclaredline has to change, that one line is cross-lanedomain:cli, declared by this seat before the queue.Fixes #23696, then anos-dev-reportcomment here.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{ "issue": 23696, "status": "done", "branch": "claude/issue-23696-sqlite-uncreatable-path", "pr": "https://github.com/objectstack-ai/objectstack/pull/23730", "session": "session_01CxHmGdRW1sJtL25b6ysocg", "premise_still_valid": true, "summary": "H1 reproduced on f40d877f8: a mapped sqlite datasource at /proc/os-23696/ds.db was refused only after 122.7 s, by 'Plugin com.objectstack.engine.objectql start timeout after 120000ms', using 83.2 s of system CPU, and neither message named the datasource, the path or the error. The root cause is Node v22.22.0's own recursive mkdir in SqlDriver.ensureDatabaseExists, not a knex pool retry, so H2 is falsified: mkdir(child) gives ENOENT, mkdir('/proc') gives EEXIST, statx('/proc') finds a directory, and it retries the child. That is 1,640 failing mkdir calls a second; strace shows it, and a bare node -e of the call reproduces it. A second arm sat beside it: SQLITE_CANTOPEN on the connect's first PRAGMA was logged and swallowed, so connect() resolved in 11 ms on an unopenable file. The fix is in packages/drivers/driver-sql only. A new sqlite-database-file.ts makes the directory with a walk bounded by the path's depth (non-recursive mkdir per missing level), and SqliteDatabaseFileError names the file, the directory and the OS or SQLite error. SQLITE_CANTOPEN now fails connect() the same way, and the native-load step-down is unchanged. The ADR-0062 D5 refusal that already exists carries the message, so app-plugin.ts is not touched. After the fix the boot is refused in 2.6 s of process wall (the connect itself fails in 1 to 7 ms), using 0.3 s of system CPU, naming 'primary', the path and ENOENT. CONTROL boots. The card's assignee field was not written.", "tests": "Pin: packages/drivers/driver-sql/src/sql-driver-23696-sqlite-uncreatable-file.test.ts, 4 tests: /proc ENOENT (runIf /proc/self), ENOTDIR plus EEXIST through a regular file, SQLITE_CANTOPEN on a directory-as-filename, and CONTROL with a 3-level fresh path that connects and answers SELECT 1. Each refusal asserts the SqliteDatabaseFileError instance, its code and filename, a message holding the file and the cause, and arrival under 1000 ms. Result: 'Tests 4 passed (4)'. Ablation used scripts/ablation-replace.mjs on fa0158df: 'ok mutation landed', then restored with 'blob after restore 4349b8b5bdd4…' equal to the blob at HEAD and an empty porcelain. Leg A swapped makeDatabaseDirectory for a recursive mkdir: 2 failed / 2 passed, with 'connect() had not settled after 5000 ms' at 5007 ms and \"expected 'EEXIST' to be 'ENOTDIR'\". Leg B swapped the SQLITE_CANTOPEN throw for a no-op: 1 failed / 3 passed, with 'connect() resolved for a file that cannot be created or opened'. The mutated lines are byte-identical on the final head, and src is imported directly with no dist involved. Package on 9f89d446: 'pnpm --filter @objectstack/driver-sql test' gave 'Test Files 221 passed | 11 skipped (232)' and 'Tests 3663 passed | 208 skipped (3871)', VERDICT command-exit 0. typecheck and build gave VERDICT command-exit 0 against a spec/dist rebuilt with .d.ts, and check-dts-emitted read 1/1. Boot with the built CLI (serve, production, OS_DATABASE_URL=file:./ctl.db), before on f40d877f8 and after on 9f89d446. H1 (mapping to /proc/os-23696/ds.db): before 122.7 s with an engine start timeout and 28.6 s user / 83.2 s sys CPU; after 2.6 s, \"datasource 'primary': connect failed — SQLite database file '/proc/os-23696/ds.db' cannot be created: its directory '/proc/os-23696' could not be made: ENOENT … ⇒ fail-fast per ADR-0062 D5\", 0.3 s sys. CANTOPEN (/proc/ds-23696.db): after 2.8 s, 'cannot be opened or created: unable to open database file'. H3b (OS_DATABASE_URL=file:/proc/os-23696/ctl.db): before 32.5 s with a default-datasource init timeout and 20.5 s sys; after 2.7 s with \"datasource 'default': connect failed … ENOENT … (declared boot-critical …)\". H3a (autoConnect, unrouted): before 33.2 s with an AppPlugin start timeout and 21.0 s sys; after a ready banner at 3.9 s (it degrades per D5). CONTROL (./ds-primary.db): ready at 4.2 s before and 4.4 s after. Driver dist: SqliteWasmDriver on /proc/os-23696/ds.db is rejected in 3 ms through super.connect().", "gates": "Local, re-derived on 9f89d446 with no paths: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' gave a change set of 4 paths against merge base f40d877f8 and 67 commands, the same 67 as at dispatch. All 67 ran, each exit code captured before any pipe. The --ran reconciliation (COMMAND :: exit N) exited 0: '✓ dispatch-gates --ran: 67 derived famil(ies) accounted for — 66 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).' 66 exited 0. NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3, 'PREREQUISITE NOT MET' (it needs a whole-repo build; studio, client-react and others have no dist), which CI runs. Three runs needed a fix or a rebuild first. check:dispatcher-error-vocabulary went red on an invented { code: 'ENOTDIR' } literal; fixed in 9f89d446 by letting the OS answer, now exit 0. check:dts-closure went red only because I had built the CLI closure with OS_SKIP_DTS=1; after a declaration build it exits 0 with '161/161 declared declaration file(s) present'. check-plugin-teardown-shape --self-test exited 3 because its fixture commit is not in this shallow clone; after fetching 621a4876 it exits 0. check:type-check-debt exited 0 with 'none above its recorded number'. CI on PR #23730 when this report was written: 'No other open PR may claim the same issue' success and 'Packed-tarball smoke (opt-in)' skipped; the other 21 check-runs (Lint ×8, Type Check ×4, Check Changeset, Check PR Size, Governed Surface Queue Guard, card-claim, Part-of, single-writer, docs links, docs-affected, filter) are in_progress. Test Core and Build Core had not been created yet.", "files_changed": [".changeset/23696-sqlite-uncreatable-path.md", "packages/drivers/driver-sql/src/sql-driver-23696-sqlite-uncreatable-file.test.ts", "packages/drivers/driver-sql/src/sql-driver.ts", "packages/drivers/driver-sql/src/sqlite-database-file.ts"], "line_budget": "+223 / -3 across 4 files (git diff --numstat f40d877f8..9f89d446)", "deviations": [ "Branch setup: the designated branch claude/issue-23696-sqlite-uncreatable-path was already checked out, with no commits, in the shared primary checkout. To create the worktree on that name I detached the primary checkout and deleted its local branch (same sha, f40d877f8). I then created the branch in ../objectstack-issue-23696 from origin/main and pushed it empty before any edit.", "Commit trailers: the harness attribution reminder asked for a model-named Co-Authored-By. I used the repo's model-free pair (Claude-Session plus Co-authored-by: Claude), as the role file and AGENTS.md require, and the pre-push gate confirmed it.", "PR subscription: the platform posture says to subscribe to a created PR. I followed the role file, which overrides that posture: no subscription, and no waiting on CI.", "PR body slip, not PATCHed. For the seat to write if wanted, the exact old text is 'Two families needed a fix or a rebuild before they went green, and both are recorded:' and the new text is 'Three families needed a fix or a rebuild before they went green, and all three are recorded:'.", "Labels: zero label writes. The dispatch named none, and the changeset publishes a patch to @objectstack/driver-sql, so skip-changeset does not apply." ], "mcp_calls": "5 — all read-only: mcp__github__issue_read ×3 (#23696 get, get_comments, and the report read-back), mcp__github__pull_request_read ×2 (#23730 get, get_check_runs)", "api_writes": "2 relay strokes. Each is POST /repos/objectstack-ai/objectstack/dispatches, run as objectstack-fleet[bot] by fleet-write.yml. Stroke 1 executed pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/23730/assignees (sunhaolin). Stroke 2 executed comment, which is POST /repos/objectstack-ai/objectstack/issues/23696/comments (this report). git push is not counted as a REST write.", "open_questions": [ { "question": "Clause-②: the claim says no. The SQLITE_CANTOPEN arm moves connect() from resolving (the datasource counted as connected but every query failed) to rejecting at once. Is that a published accept set moving?", "options": ["A: keep 'Clause-②: no'. No authorable key or value changes. ADR-0062 D5 and the #4462 amendment already declare an unconnectable declared datasource fatal, so the old resolve was a declared-but-not-enforced gap.", "B: declare 'Clause-②: yes (narrowing)' and bump the changeset to minor plus breaking."], "recommendation": "A, on the four axes. Real business need: the only producer is an operator's typo in a path, and every config that hit this arm already failed every query. Long-term soundness: it enforces the contract that is already declared. AI error-proofing: a loud refusal at connect beats a datasource that reports connected but cannot answer. Startup no-sprawl: no new key or gate. The changeset states the before and after in prose." }, { "question": "Hypothesis H3 asked to fix the producer once so every caller gets the open error. The driver fix covers serve's mapped connect, serve's default datasource from OS_DATABASE_URL, and the wasm driver. But 'os start' reaches a second producer: the synchronous mkdirSync recursive call in packages/runtime/src/standalone-stack.ts. It is outside the claimed file surface and gate family. Fix it here or file it?", "options": ["A: the seat files it as its own card in the runtime lane (outside the domain:cli or engine claim), reusing makeDatabaseDirectory's shape as a sync walk or by awaiting it.", "B: widen this PR into packages/runtime."], "recommendation": "A. The in-place exemption fails condition ④: a new package means a new test and gate surface. The claim's file surface excludes it, and the role file says to report it rather than touch it." } ], "out_of_scope_findings": [ "class: a · reach: public door 'os start' (built CLI, no config, OS_DATABASE_URL=file:/proc/os-23696/ctl.db) prints '→ Starting server...' and then hangs. It was killed at 25 s, with no timeout or refusal. Under strace, the respawned server's main thread made 21,443 failing mkdir(\"/proc/os-23696\") calls in 12 s, which is a synchronous loop, so no plugin timeout can fire. · evidence: producer packages/runtime/src/standalone-stack.ts createStandaloneStack, sqlite arm 'mkdirSync(resolvePath(filename, ..), { recursive: true })' (line 789 at f40d877f8). The sqlite-wasm arm (line 775) has the same line. packages/cli/src/commands/dev.ts:577 ('os dev') has the same shape, unmeasured. Same defect class as #23696 (Node recursive mkdir loops on ENOENT-under-existing-parent), different producer. ADR-0062's #3826 resolution names this mkdir as a host concern. · dedupe words: os start hangs sqlite database url uncreatable directory; standalone stack mkdirSync recursive never returns; OS_DATABASE_URL file path proc hang no timeout", "carrier: none (承接者:无) · noted, not filed: H3a (a declared sqlite datasource with autoConnect: true, not addressed by any mapping, binding or bootCritical) now degrades instead of hanging, per ADR-0062 D5. Its 'degrading (datasource left unconnected)' warning did not appear on serve's stdout or stderr even at OS_LOG_LEVEL=warn. That is visibility of an existing degrade path, unmeasured beyond that stream; it is in the PR's Acceptance notes.", "carrier: none (承接者:无) · noted, not filed: the dev probe comment in packages/services/service-datasource/src/sqlite-driver-fallback.ts ('a PRAGMA whose error it swallows') is now true except for SQLITE_CANTOPEN. The probe's behaviour is unchanged, because its catch handles a throwing connect() and the old throwing SELECT 1 alike. It is in the PR's Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23730 comment 6204105974 ·
domain:engineseat 1 ·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T23:04Z. This is the seat's review of record:Clause-②: no, the implementation pulled back to ADR-0062 D5, and no export added. The second producer (os start'smkdirSync) is filed as #23737. Landing follows through the merge queue.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed ·
domain:engineseat 1 ·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T23:37Zpr_ready+automerge_enablethrough the fleet-write relay.53646fc4b5's commit time, 2026-10-15T23:07:17Z, is the enqueue. After the merge,git ls-remotelists onegh-readonly-queuebranch, and it is PR #23704's (pr-23704-53646fc4b5…). None is left for PR #23730.origin/main53646fc4b5is onorigin/main(git merge-base --is-ancestorexit 0). It is the squash of PR #23730: the same 4 files, +223/−3, all inpackages/drivers/driver-sql. The merge event and this card's close were both at 2026-10-15T23:35:53Z.domain:skillscards assigned elsewhere (#23662, #23607). None is this lane's. Nothing unexpected.Status: #23696 is closed, and its
pm:dispatchedis removed in this stroke. The cloud dev sessionsession_01CxHmGdRW1sJtL25b6ysocgis archived.Follow-up: the second producer,
os start's synchronous recursivemkdirSyncinpackages/runtime/src/standalone-stack.ts, is #23737 (finding, the runtime lane). It was ordered after this PR, and that order is now met.Generated by Claude Code