PR #650 — Sandbox DB Fallback: Don't Crash When $HOME Is Read-Only
The Broader Goal at the End of the Previous PR
synlynk's project state lives at ~/.synlynk/projects/<md5>/state.db so all worktrees of one repo share a single ledger on one machine. That design is correct for local orchestration. What it never fully accounted for is a dispatched agent running inside its own sandbox: different $HOME, often a read-only mount outside the workspace, and no copy of the orchestrator's .synlynk/config.json or DB.
Issue #648 made that gap a hard crash. A Codex review job in cc-videoreframing ran synlynk status and died with sqlite3.OperationalError: unable to open database file. Local status/doctor on the same machine were fine — so investigation time was burned re-checking a migration that was never broken.
Strategic Shifts in This PR
None on the long arc. This is a reliability fix on the same theme as #645 (state that is real on one machine and absent/broken elsewhere): fail soft in sandboxes instead of looking like a corrupted install.
One nuance vs. the issue body's source snapshot: main already had an OperationalError fallback on _get_db() (from the #452-era rewrite). The remaining crash window was narrower but real — os.makedirs on a read-only $HOME raises plain OSError(EROFS), which is not a PermissionError subclass, so the previous except PermissionError never fired. When the directory already existed, connect's OperationalError was caught — but with no warning, so a fresh empty local DB looked like silent data loss rather than "this machine has no project state."
What This PR Shipped
_get_db()catches(OSError, sqlite3.OperationalError)— one branch covering EROFS/ENOSPC/PermissionErrorand "unable to open database file."- One-line stderr warning on fallback distinguishing "no project state found on this machine" from genuine corruption, and naming both the primary and fallback paths.
- Regression tests (
tests/test_get_db_sandbox_fallback.py): EROFS from makedirs, PermissionError, connect OperationalError, and re-raise when both primary and fallback fail.
Out of scope (called out in #648 suggestion 3, not implemented here): whether dispatched jobs should call DB-backed subcommands at all, or how sandbox config null-org/repo defaults should be provisioned. Those need a dispatch-harness design decision, not a quiet DB patch.
Brainstorm Visuals Used
None.
What This Achieved on the Path to Autonomy
Agents that run synlynk status / doctor inside a sandbox no longer die with an opaque SQLite error that looks like a migration regression. They get a local empty DB plus an explicit warning. That keeps investigation on real product bugs instead of environment mismatches — the same failure-mode family as #645, closed as a crash instead of a silent no-op.
The Goalpost at the End of This PR
Sandbox DB access is degrade-safe. Still open: deliberate policy for "should a dispatched job ever read/write project state," and provisioning .synlynk/config.json (or a read-only view of the orchestrator DB) into the sandbox when the intent is shared state rather than isolation.