Repository navigation
fix(tui): resolve the state root before opening session-side state - #14
SparkofSpike wants to merge 4 commits into
Conversation
ReviewVerdict: the fix is correct and complete for the reported failure. Save on a relocated state root now gets past approval-receipt hydration. Traced path: Trust boundary. Correct per file:
Findings
Unlinked case: unchanged. On Unix Nothing missed on save/load: no other confined open sits under the state root there, and Attribution🤖 Generated by SpikeBot 003(ClaudeCode-JP) |
Follow-up reviewNit 1 is closed. Commit
No case found where the two disagree and the wrong opener wins. The only construct that can hit the guard with a non- Minor, non-blocking:
No redundancy worth removing, and no remaining site in this PR passes a root it does not own: Unrelated to the code: the Attribution🤖 Generated by SpikeBot 003(ClaudeCode-JP) |
A user who relocates application state to another volume cannot save a session at all. Every save fails with the red "cannot save session" notice and the log records persistence write failed what=session:<id> error_kind=InvalidInput error=Fleet artifact path must stay within the workspace `SessionManager::save_session_inner` hydrates approval receipts before it serializes anything, and `ApprovalReceiptStore::log_file` opened `<sessions>/<id>/approval_receipts.jsonl` through the strict `WorkspaceFile::open`. On Windows that opener walks the root component by component and refuses the junction that is the state root, so the save died before producing a document. Storing session receipt data went from plain file I/O (0.10.2) to the confined opener, which is the right direction, but the state root is a root the user owns and may relocate. `open_resolved_root` exists for exactly that (codewhale-hq#6947): it resolves the root once and then runs the same link-refusing walk below it. These call sites now use it: - approval receipt log (the one that blocked saving) - session archive artifact reads and the two directory checks - clipboard image sink, screencapture attachment sink - pet habitat store, descriptor and lifetime lock No boundary is widened: a link below the state root is still refused, and `WorkspaceFile::open` stays the opener for caller-supplied workspace roots. Verified with cargo check -p codewhale-tui (exit 0) and cargo fmt. The 0.10.2 binary with the same junction layout reproduces the failure, so this is not a regression from an unreleased branch.
The review of the previous commit found that two of the converted call sites do not always hold the state root. `clipboard_images_dir_for_home` and `screenshot_stabilization_dir` fall back to a directory under the workspace when no home directory resolves, so `dir.parent()` is then a caller-supplied workspace root — and resolving it would have widened the rule that a link at the workspace root is refused. Both sites now pick the opener from the root they actually hold: the resolved opener only for `<home>/.codewhale`, the strict opener otherwise. The normal path (a home directory exists) keeps the junction fix; the narrow fallback keeps the boundary it had.
4d7cd86 to
3c31785
Compare
8155802 added three Unreleased entries to CHANGELOG.md without running scripts/sync-changelog.sh, so the Version drift check fails on every PR based on main (seen on codewhale-hq#7097 and codewhale-hq#7094). Regenerated from origin/main's root CHANGELOG.md; scripts/sync-changelog.sh --check passes on the result. Built with a temporary index so the shared checkout's uncommitted work is untouched. Co-Authored-By: Claude Opus 5.5 <[email protected]>
fix(tui): resolve the state root before opening session-side state files
Summary
A user who relocates application state to another volume — the documented way
to keep
~/.codewhaleoff the system drive — cannot save a session at all.Every save fails with a red "cannot save session" notice, and the log says:
The save itself never starts.
SessionManager::save_session_innerhydrates theapproval receipts before it serializes anything, and
ApprovalReceiptStore::log_fileopens<sessions>/<id>/approval_receipts.jsonlthrough the strict
WorkspaceFile::open. On Windows that opener walks the rootcomponent by component and refuses the junction that is the state root, so
every save dies on a state directory the user deliberately relocated.
Changes
approval_log.rs: the receipt log opens throughWorkspaceFile::open_resolved_root, the opener added in Workflow resume: replay completed steps from a prior run codewhale-hq/Codewhale#6900 for exactlythis case. The root is canonicalized once; every component below it is still
refused when it is a link. This is the change that unsticks session saving.
session_export.rs: the artifact reads and both directory checks in asession archive take the same opener, so
sessions exportworks on the samelayout instead of failing where saving already failed.
tui/clipboard.rs,tui/file_mention.rs,tui/pet_watch/*: theclipboard image sink, the screencapture attachment sink and the pet habitat
store all sit under the state root too. Same one-call change each; the
link-refusing walk below the root is unchanged. The two sinks that fall back
to a workspace root when no home directory resolves pick the strict opener
for that fallback, so a caller-supplied root keeps its old rule.
Nothing widens the boundary:
open_resolved_rootresolves the root and thenruns the same per-component refusal, so a link below the state root is still
an error.
WorkspaceFile::openremains the opener for caller-suppliedworkspace roots.
Reproduction
%USERPROFILE%\.codewhalea junction to another volume(
mklink /J %USERPROFILE%\.codewhale D:\CodeWhaleData)./save.logs/tui-*.logrecordspersistence write failed ... error=Fleet artifact path must stay within the workspaceonce per attempt, with no
sessions/<id>.jsonwritten.The same failure reproduces on 0.10.2 without any repository change, so this is
not a regression from an unreleased branch.
Testing
cargo check -p codewhale-tui— exit 0, no warnings.cargo fmt --all -- --check— clean.cargo build --release --locked -p codewhale-cli -p codewhale-tui— buildsthe shipped binaries from this branch.
%USERPROFILE%\.codewhaleleft as a junction toD:\CodeWhaleData:codewhale exec --auto "Run the shell command: echo CWJUNCTION_OK"finishedwith exit 0 and wrote
sessions/cf02b940-8436-4289-b16b-756d7a516228.json(58,986 bytes) through the junction. Before the change the same state root
produced
persistence write failed what=session:... error=Fleet artifact path must stay within the workspaceand no document.(
linked_state_root_resolves_once_and_keeps_the_inner_ruleinfleet/files.rs) already pins the property this change relies on: theresolved-root opener writes through a root junction and still refuses a link
below it. It was not run locally on this machine — the
codewhale-tuitestbuild runs out of memory here — and PR CI's Windows lane only runs
cargo check, so treat that test as inherited rather than re-verified.Checklist
open_resolved_rootis the same call as
open).sinks that can fall back to a workspace root keep the strict opener
there.
by
open_resolved_root.署名
🤖 由 SpikeBot 000(CodeWhale-LOCAL) 生成