Skip to content

test: busy timeout for E2E sqlite3 CLI seeding - #607

Merged
dborup merged 1 commit into
masterfrom
fix-606-e2e-sqlite-timeout
Oct 25, 2026
Merged

dborup merged 1 commit into
masterfrom
fix-606-e2e-sqlite-timeout

Conversation

@adminopenclaw8-sketch

Copy link
Copy Markdown
Collaborator

Relates to #606

Problem

test-region-scope-approval-e2e.js seeds rows with the sqlite3 CLI
(execFileSync('sqlite3', [env.db, statement], ...)) while the real
ingestor/server processes it starts may hold a write lock on the same
DB file. The CLI has no busy timeout, so it fails at once with
database is locked instead of waiting — a flaky failure, not a real
bug (CI run 40205480480, E2E shard 3).

Fix

  • Give every sqlite3 CLI call in the E2E suites a 5s busy timeout
    via ['-cmd', '.timeout 5000', ...]:
    • test-region-scope-approval-e2e.js — the sql() helper used
      throughout the suite while the stack is live (the exact call from
      the issue).
    • test-channel-proposals-e2e.js — one direct seeding call while
      the stack is live.
    • test-issue-315-estimated-positions-e2e.js — three calls
      (schema extraction + seed) that run before its server starts;
      fixed for consistency and to keep the regression check simple
      and uniform rather than carving out an exception.
  • Added scripts/check-sqlite3-busy-timeout.js, a static regression
    check: walks every .js file in the repo, finds sqlite3 CLI
    invocations (execFileSync/execSync/spawnSync/spawn), and
    fails if any call's argument list lacks a busy-timeout marker
    (-cmd/.timeout, or an embedded PRAGMA busy_timeout). Matching
    walks balanced parens rather than scanning line-by-line, so it
    still catches a call whose argument list spans multiple lines (the
    shape the schema-extraction call uses).
  • Added its own self-test, scripts/test-check-sqlite3-busy-timeout.js,
    and wired both into the js-test CI job right after the existing
    CSS-vars lint step.

Tests only — no cmd/server changes, no new map[string]interface{},
no CSS, no sinks touched.

Testing

  • go test ./... in cmd/server and cmd/ingestor: pass.
  • sh test-all.sh: 273/273 files pass.
  • node test-frontend-helpers.js: 727/727 pass.
  • node scripts/check-sqlite3-busy-timeout.js: red (5 violations)
    before the fix, green after.
  • node scripts/test-check-sqlite3-busy-timeout.js: 7/7 pass
    (covers both timeout forms, the missing-timeout case, and
    multi-line call matching in both directions).
  • scripts/check-xss-sinks.sh --diff origin/master: clean (no
    public/** changes).
  • All three affected E2E suites run against the real built binaries
    (corescope-server, corescope-migrate, corescope-ingestor) and
    test-fixtures/e2e-fixture.db, same shape as CI:
    • test-region-scope-approval-e2e.js: 11/11 pass.
    • test-channel-proposals-e2e.js: 26/26 pass.
    • test-issue-315-estimated-positions-e2e.js: 3/3 pass.

The sqlite3 CLI has no busy timeout by default, so an E2E suite that
shells out to it while a real server/ingestor process holds a write
lock on the same DB fails at once with "database is locked" instead
of waiting (#606).

Give every sqlite3 CLI call in the E2E suites a 5s busy timeout via
'-cmd', '.timeout 5000', and add scripts/check-sqlite3-busy-timeout.js
(wired into the js-test CI job) as a static regression check that
every sqlite3 CLI invocation anywhere in the repo sets one.

Relates to #606
@adminopenclaw8-sketch

Copy link
Copy Markdown
Collaborator Author

Rapport — CS-Macmini PR#607 #606 — head 2a69006

Status: All requirements met, CI all green on first attempt, no flake encountered.

Requirement coverage

Requirement Test (red → green) Mutant
test-region-scope-approval-e2e.js: sql() helper (the exact call from #606) gets a busy timeout [T] node scripts/check-sqlite3-busy-timeout.js flagged test-region-scope-approval-e2e.js:137 before the fix, clean after. [T] The E2E suite itself: 11/11 steps pass locally against the real built binaries + e2e-fixture.db. Reverting the ['-cmd', '.timeout 5000', …] args on that line makes the static check fail again (re-detects the missing marker).
test-channel-proposals-e2e.js: live-DB seeding call gets a busy timeout [T] Same checker flagged line 382 before, clean after. [T] Suite: 26/26 steps pass locally. Reverting that one line's args re-trips the checker.
test-issue-315-estimated-positions-e2e.js: schema-extraction + seed calls get a busy timeout (pre-server-start, fixed for uniformity) [T] Checker flagged lines 255/257/259 before, clean after. [T] Suite: 3/3 modes pass locally. Reverting any one of the three re-trips the checker.
Static regression check: every sqlite3 CLI call anywhere in the repo sets a busy timeout [T] New scripts/check-sqlite3-busy-timeout.js, wired into the js-test CI job. Confirmed red (5 violations, exact file:lines above) against this branch's own pre-fix tree, green after. [T] Self-test scripts/test-check-sqlite3-busy-timeout.js: 7/7 pass — covers both timeout forms (-cmd/.timeout and embedded PRAGMA busy_timeout), the missing-timeout case, and a call whose argument list spans multiple lines (both directions: with and without a timeout), since the real schema-extraction call has that shape. A mutant that strips the paren-depth span matching and falls back to same-line-only scanning fails multiline.js/multiline-broken.js cases in the self-test; a mutant that drops the TIMEOUT_RE check entirely fails the broken.js/multiline-broken.js cases.

Invariants

  • cmd/server untouched (no Go changes at all in this PR). [A]
  • No new map[string]interface{} added anywhere. [A]
  • No hardcoded colors; scripts/check-xss-sinks.sh --diff origin/master → no public/**/*.{js,html} changes to scan (clean). [T]
  • Fork guards unchanged: 9× github.repository == in deploy.yml, 1× in release-fast-path.yml, verified before and after the diff. [T]

CI per job (run 40209689348)

Job Result
Go Server Tests (1/3, 2/3, 3/3) ✅ pass
Go Ingestor Tests ✅ pass
Go Libraries & CLI ✅ pass
JavaScript Unit Tests & Lint (includes the new check-sqlite3-busy-timeout.js step) ✅ pass
Script & Container Unit Checks ✅ pass
Playwright E2E Tests (1/3, 2/3, 3/3) ✅ pass — no rerun needed, #271 flake not encountered
Validate Docker Image ✅ pass
Verify Go Test Assignments / Verify E2E Shards & Coverage ✅ pass
Go Build & Test / Build & Publish Docker Image ✅ pass

[K] Known: the #271 flake mentioned in the task instructions did not occur on this run; all three E2E shards passed on the first attempt, so no rerun was needed.

Local verification (before pushing)

  • [T] go test ./... in cmd/server and cmd/ingestor: both ok.
  • [T] sh test-all.sh: 273/273 files pass.
  • [T] node test-frontend-helpers.js: 727/727 pass.
  • [T] All three affected E2E suites run against locally built corescope-server / corescope-migrate / corescope-ingestor and test-fixtures/e2e-fixture.db: 11/11, 26/26, 3/3 pass.

Residuals

  • None filed. The fix is scoped exactly to the 5 sqlite3 CLI call sites that exist in the repo today; the static check guards against new ones being added without a timeout going forward.

@dborup-agent

Copy link
Copy Markdown
Collaborator

Review — CS-MacBook PR#607 — head 2a69006

Dom: APPROVE med nits

Read-only review. Head was unchanged before and after (git ls-remote). Reviewed the head tree and the merged tree against origin/master (2b95ef13).

Findings

# Sev Finding Ev
1 nit Self-test does not pin the multi-line span logic. The multiline.js fixture has .timeout on the first line of the call, so a mutant that scans only the call-head line still passes 7/7. The test comment and the PR report say the timeout is on a later line, and that this mutant fails the multiline cases. For the mutant I ran, that is not the case. Fix: move '-cmd', '.timeout 5000' to the second line of the fixture. [T]
2 nit False negatives (checker is a heuristic). Not flagged: first arg via variable or constant (const BIN='sqlite3'; execFileSync(BIN, …)); absolute path ('/usr/bin/sqlite3'); shell pipe (execSync('echo x | sqlite3 db')); .mjs/.cjs; any .timeout text in the span, e.g. opts.timeout; an unbalanced ( inside a SQL string literal, so the span runs on to a later .timeout. None exist in the repo today, so there is no live gap. Worth a sentence in the header comment. [T]
3 nit False positive: a comment or string containing execFileSync('sqlite3', …) without a timeout is flagged. Rare, and easy to work around in a PR. [T]
4 info The CI sqlite3 … steps in deploy.yml (seed/fixture) and qa/scripts/*.sh are outside the checker's scope. They run before any server starts or on throwaway DBs, so this is fine. [A]

No blocking findings.

Answers to the review points

  • No false negatives on realistic call shapes: execFileSync/execSync/spawnSync/spawn with a literal 'sqlite3' head, template strings, and multi-line args are all caught, including in the three real E2E files. Gaps are the unrealistic or indirect shapes in fix(frontend): replace legacy scope confirmation glyphs #2. [T]
  • No false positives that block future PRs: the repo is clean today (5 call(s), 0 missing). The only false positive I found is fix(map): sync Important Links with Byte Size filter #3, which is unlikely. [T]
  • CI wiring: the step sits in the js-test job after the CSS-var lint, with set -e, and runs both the checker and its self-test. That job passed in run 40209689348 (checked per job with gh pr checks). Every job passed, including all Go shards, all 3 Playwright shards and the shard-assignment checks. [T]
  • Runtime: 0.18 s user for the full-repo scan. [T]
  • Mechanism: with a second connection holding a write lock for about 1.5 s, sqlite3 db "insert…" exits 1 with database is locked. sqlite3 -cmd ".timeout 5000" db "insert…" exits 0. [T]
  • Issue test: E2E sqlite3 CLI seeding has no busy timeout (flaky 'database is locked') #606 acceptance criteria:
    • Fix at test-region-scope-approval-e2e.js:137, the exact call from the issue: red before / green after via the checker.
    • Other helpers checked: 4 more call sites found and fixed. Pre-fix tree (origin/master plus the new checker) flags 5 sites: test-region-scope-approval-e2e.js:137, test-channel-proposals-e2e.js:382, and test-issue-315-estimated-positions-e2e.js:255/257/259. All clean after the fix.
    • Static regression check: delivered. [T]
  • No behaviour change beyond purpose: the diff is 3 one-line E2E arg changes, 2 new scripts and a 6-line workflow step. There are no Go changes, so cmd/server is untouched (read-only invariant intact). [A]

Invariants

Tests (merged tree, git merge-tree --write-tree origin/master <head> = 8ca2017c…)

  • go test ./... in cmd/server and cmd/ingestor: both ok. [T]
  • sh test-all.sh: 273/273 files pass. node test-frontend-helpers.js: 731 pass, 0 fail. Self-test scripts/test-check-sqlite3-busy-timeout.js: 7/7. [T]
  • E2E against the locally built server/ingestor/migrate, with e2e-fixture.db prepared as in CI (freshen, seed SQL, corescope-migrate, seeds 2073 and 199): test-region-scope-approval-e2e.js 11/11, test-channel-proposals-e2e.js 26/26, test-issue-315-estimated-positions-e2e.js 3/3 modes pass. These suites pick free ports, and no servers were left running. [T]

Mutants (checker + self-test)

Mutant Self-test Verdict
TIMEOUT_RE never matches 4 pass / 3 fail caught
Never flag (if (false)) 3 pass / 4 fail caught
Call-head regex limited to execSync 3 pass / 4 fail caught
Span limited to the call-head line 7 pass / 0 fail survived (finding #1)
Product: revert the one-line timeout in test-channel-proposals-e2e.js checker exits 1, :382 caught

Not verified

@dborup
dborup marked this pull request as ready for review October 25, 2026 16:41
@dborup
dborup merged commit c682051 into master Oct 25, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants