fix(engine): create the database's parent directory before connecting - #11
Merged
Conversation
Store.connect() called aiosqlite.connect(db_path) directly. sqlite does not create missing intermediate directories, and the default paths (data/engine.db in run.py, data/web.db in main.py) live under data/, which is gitignored and so absent on a fresh clone or CI runner. The engine died with "unable to open database file". This was red on main: CI backend (3.11) failed test_start_project_emits_project_created, and the e2e job's web server threw the same OperationalError on a loop. It survived local verification for two compounding reasons: every store test builds its path under pytest's tmp_path, which already exists, and any developer who has run the engine once already has a data/ directory. Confirmed by A/B with data/ removed -- the CI failure reproduces exactly without this change and passes with it. Fixed in Store.connect() so every caller benefits, rather than at the two db_path defaults. tests/engine/test_store_db_path.py covers the missing parent, nested parents, and a bare filename with no directory component (which must not trip the mkdir). Backend suite 152 -> 155; full suite verified with data/ absent.
… pointer CI on this PR showed the e2e job now fails with 'claim_next_task failed: database is locked' rather than the missing-directory error -- a second, undiagnosed defect that the first one was masking. Logged as a known gap and promoted to the top next step, with the busy_timeout explanation explicitly ruled out. Also: the state summary still pointed at publication-prep, a branch that no longer exists; black file count 66 -> 67.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
mainis currently red, and a fresh clone of AppForge cannot start the engine. This fixes both with a one-line change plus the regression test that should have caught it.The bug
Store.connect()calledaiosqlite.connect(db_path)directly. sqlite does not create missing intermediate directories, and both default paths live underdata/:backend/engine/run.py:50→data/engine.dbbackend/main.py:99→data/web.dbdata/is gitignored (.gitignore:273) and untracked, so it does not exist on a fresh clone or a CI runner. The engine dies withOperationalError: unable to open database file.Evidence it is real
test_start_project_emits_project_createdfails — 1 failed, 151 passedbackend (3.11)onmain@ae59b29OperationalError: unable to openrepeatedlye2eonmain@667e21fA/B reproduction locally, with
data/moved aside:Why the suite did not catch it
Two things compounded:
tmp_path— which pytest has already created. The missing-parent case was never exercised.data/. Local runs pass for a reason that does not hold on a fresh checkout.So the green local suite was accurate and still missed this. Only CI, running on a clean runner, saw it. The new test closes that gap by constructing the fresh-clone condition explicitly.
The fix
In
Store.connect()— the single point every caller passes through — rather than patching the twodb_pathdefaults independently:The guard matters: a bare filename like
run.dbyields a parent of., and callingmkdiron it is pointless noise.Tests
tests/engine/test_store_db_path.pycovers three cases, and the file documents why the gap existed so it is not reintroduced:data/fresh-clone condition)a/b/c/run.db)Verified these fail without the fix — both directory cases raise the exact
sqlite3.OperationalError: unable to open database file.Verification
Full suite run with
data/removed, i.e. true CI conditions:uv run pytest tests/(nodata/)uv run ruff check backend/ tests/uv run black --check backend/ tests/README badge and
docs/STATUS.mdcounts updated 152 → 155.Scope note — corrected after CI ran
I originally expected
e2eto stay red because its Playwright specs predate the engine rewrite. CI shows that was wrong, and the correction is worth stating plainly.This fix did clear the
unable to open database fileerror from the e2e web server. What it revealed underneath is a different failure:So
e2eremains red, but for a reason this PR neither causes nor fixes: the web bridge startsstart_run(workers=4)againstdata/web.db, and under CI those four worker processes hit lock contention onclaim_next_task.I have not diagnosed the root cause and am not guessing at one here. Worth recording: a missing
busy_timeoutpragma is not the explanation, since Python'ssqlite3.connect()already applies a 5s busy timeout thataiosqliteinherits. This warrants proper investigation as its own piece of work.Effect of this PR on CI:
backend (3.11),backend (3.12),frontend, andvalidate-configall pass —backend (3.11)being the job that was red onmain.e2ewas already red before this PR and stays red.