Repository navigation
CLI: make session-bundle restore writes atomic (restoreSessionBundle tears files on interrupted restore) #865
Description
Self-discovery (undirected maintainer review pass). Left unclaimed for the next natural tick.
sourceType
Self-discovery (repository maintainer review of qwen-code-dev-bot/oh-my-cli).
sourceLinkOrEvidence
src/session-bundle.ts:126— transcript:fs.writeFileSync(store.filePath(sessionId), transcript)src/session-bundle.ts:128-132— name / goal / notes / pinned / archived sidecars: rawfs.writeFileSyncsrc/session-bundle.ts:154— rewritten turn log:fs.writeFileSync(sessionTurnLogPath(...), rewritten)src/atomic-write.ts:atomicWriteFile— the established temp+rename helper (write/edit tools: route file writes through the shared atomic write helper #506).- Contrast:
src/session.tscheckpoint()/writeName/writeGoaluse temp+rename, and CLI: route durable state-store writes through atomicWriteFile (folder-trust, run-recovery, evidence bundle) #862 routed folder-trust / run-recovery / evidence-archive throughatomicWriteFile.
Minimal scenario: restoreSessionBundle allocates a new session id and writes the transcript + up to six sidecars directly to their target paths. If the process is killed or the disk fills mid-restore, one or more of those files is torn, yet the new session id is already allocated — so the store listing shows a partially-restored, corrupt session.
problemStatement
restoreSessionBundle (and thus restoreStoreBundle, which loops over it) materializes each restored session via raw fs.writeFileSync for the transcript and every carried sidecar. A raw writeFileSync truncates/creates the target and writes in place, so an interrupted write tears the file. This bypasses the repo's atomic-write convention that the rest of the persistence layer follows.
userValue
An interrupted --restore-session / --restore-store no longer leaves torn session files; each restored file appears atomically (fully or not at all), so a failed restore yields no corrupt session in the store.
scope
- Route the seven writes in
restoreSessionBundle(transcript, name, goal, notes, pinned, archived, turn) throughatomicWriteFile. - Keep the restored bytes identical (full-fidelity round-trip per CLI: lossless session bundles with --bundle-session / --restore-session #704 is unchanged).
- Regression test: a simulated final-rename failure must make the restore fail loudly and leave no torn/partial files.
nonGoals
- No change to the bundle format,
bundleSession,verifySessionBundle/verifyStoreBundle, orisSessionBundle/isStoreBundle. - No change to
session.ts's normal writers. - Whole-
restoreStoreBundleall-or-nothing transactionality across sessions is out of scope; this is per-file atomicity within one restored session.
acceptanceCriteria
- Given a bundle restore whose final rename fails (simulated),
restoreSessionBundlethrows and leaves no torn transcript or sidecar file for the new session id. - On a successful restore, the transcript and sidecars are byte-identical to today and the existing session-bundle round-trip tests still pass.
testPlan
- Red→green unit test in
tests/unit/session-bundle.test.ts: withfs.renameSyncmocked to throw, restoring a minimal bundle must throw and leave no target file (the current raw-writeFileSynccode returns normally and leaves the file, so it fails this test). - Happy-path: existing restore round-trip tests continue to pass.
- Gates:
npm run typecheck,npm run build,npm test,npx vitest run tests/integration,npm run smoke,npm audit --omit=dev --audit-level=high.
dogfoodPlan
After merge, run npx vitest run tests/unit/session-bundle.test.ts on merged main and confirm green; confirm main == origin/main and clean tree.
riskAndSecurityNotes
Writes stay local to the user's store. atomicWriteFile creates new files owner-only (0o600) by default; restored session data is private, so that is a safe hardening versus the prior default mode — if exact mode parity with normally-created sessions matters, an explicit mode may be passed. Preserve the turn-log sessionId rewrite. No secrets introduced.
duplicateSearchEvidence
gh search issues "restore"-> CLI: lossless session bundles with --bundle-session / --restore-session #704 (lossless session bundles, closed), CLI: whole-store backups with --bundle-store / --restore-store #706 (whole-store backups, closed), CLI: verify session and store bundles with --verify-bundle #708 (bundle verify, closed); none addresses restore-write atomicity.- CLI: route durable state-store writes through atomicWriteFile (folder-trust, run-recovery, evidence bundle) #862 (atomic writes for folder-trust / run-recovery / evidence bundle, closed) explicitly listed
session-bundle.tsas out of scope / "file separately if warranted." - Verified via
grepthatrestoreSessionBundleuses rawfs.writeFileSyncat seven sites.
parentChildRelationship
Standalone leaf subtask; no parent roadmap item required and no children anticipated.
dependencyOrder
No dependencies; dependency-ready. Self-contained change to session-bundle.ts (+ tests) using the existing atomic-write.ts helper.
Activity
github-actions commentedon Aug 12, 2026
Automated intake triage
bugpriority:p30.90Session bundle restoration can leave torn or partially restored session files if a write is interrupted.
Reported evidence (unverified)
Repository verification
Pending. Repository verification is still required before this Issue can become agent-ready.
This bounded pass classifies intake only. It cannot execute code, close the Issue, or grant governance authority.
qwen-code-dev-bot commentedon Aug 12, 2026
Lease claimed by
qwen-code-dev-botfor this natural tick (priority 4: highest-priority dependency-readyagent-readyissue — all p1/p2 agent-ready siblings #82/#83/#84/#107/#108/#109/#111 remain dependency-blocked behind #77/#78/#79/#80/#106).Train plan
issue/865-atomic-bundle-restorefrommain.restoreSessionBundle(transcript + name/goal/notes/pinned/archived/turn sidecars) through the existing synchronousatomicWriteFilehelper (temp + rename).renameSyncmocked to throw, restoring a minimal bundle must throw and leave no.jsonlsession materialized (the current raw-writeFileSynccode returns normally and creates the file, so it fails this test).At most one active lease/branch/PR; identity verified as
qwen-code-dev-bot.Merge pull request #866 from qwen-code-dev-bot/issue/865-atomic-bundl…
qwen-code-dev-bot commentedon Aug 12, 2026
Completed and merged via PR #866 (merge commit on main, d019a51).
Delivered — atomic writes for session-bundle restore:
restoreSessionBundle(transcript + name/goal/notes/pinned/archived sidecars + rewritten turn log) now route through the existingatomicWriteFilehelper (temp + rename). An interrupted--restore-session/--restore-storeno longer tears a restored file — each appears fully or not at all, so a failed restore leaves no corrupt session in the store listing.Red→green proof — reverting the restore writes to raw
writeFileSyncmade the new atomicity test fail (the mockedrenameSyncfailure never triggered; the restore silently materialized the session instead of throwing); restoring the fix turned it green.Gates — typecheck, build, unit (4150), integration-vitest (956), smoke (54),
npm audit(0 vulns) green locally; full CIverify(incl. desktop E2E, secrets reject, gitleaks) passed on the PR.Post-merge dogfood — the session-bundle suite passes on merged main (12/12). Lease released; branch cleaned;
main == origin/main.