You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracks deferred follow-up items from PR #4064 (/rewind file restoration). All real but each was large enough to belong outside the introduction PR. Sibling follow-ups: #4173 (session backup cleanup), #4187 (RewindSelector + handleRewindConfirm tests), #4216 (TOCTOU + sticky failed-marker fixes).
Current status (updated 2026-06-14)
✅ Item A — Cross-session snapshot persistence: implemented by PR fix(core): Persist file history snapshot updates #5057 (fix(core): Persist file history snapshot updates), merged on 2026-06-13 UTC. qwen-code persists file_history_snapshot records, restores them on session load, and immediately re-records the latest snapshot when trackEdit(...) mutates it.
⏳ Item B1 — simulated sed -i edit tracking: still open. claude-code supports this specific shell path by parsing simple in-place sed substitutions and applying them through a direct file write that triggers file-history tracking.
✅ Item B2 — generic shell tracking decision: decided out of scope for now. claude-code does not track arbitrary shell writes such as cp, mv, rm, git apply, package scripts, or manual edits.
✅ Item C — GeminiClient.sendMessageStream bridge tests: implemented by PR fix(core): Persist file history snapshot updates #5057. The tests cover UserQuery snapshot creation/recording, non-UserQuery and retry skips, and best-effort error swallowing.
⏸️ Item D — getDiffStats concurrency limit: deferred. claude-code also uses unbounded Promise.all; this would be qwen-code hardening beyond upstream parity.
⏸️ Item E — per-file failure reasons: deferred. claude-code logs restore failures but does not expose or persist per-file failure reasons.
Remaining active upstream-parity work: only Item B1. Generic shell tracking, diff concurrency limiting, and per-file failure reasons should stay deferred unless upstream adds a concrete design or qwen-code deliberately chooses to go beyond upstream.
Item A — Cross-session snapshot persistence ✅ Done in PR #5057
Original problem: FileHistoryService.getSnapshots() and restoreFromSnapshots() existed but were not wired into session persistence. After a process restart or /resume, the in-memory state was empty, so /rewind could not see snapshots from earlier sessions even though backup files existed under ~/.qwen/file-history/{prevSessionId}/.
Upstream reference: claude-code/src/utils/sessionStorage.ts builds a FileHistorySnapshotChain keyed by message uuid into the session transcript; claude-code/src/utils/sessionRestore.ts calls fileHistoryRestoreStateFromLog on resume to hydrate appState.fileHistory. fileHistoryTrackEdit(...) also records an update for the current message after an edit mutates the latest snapshot.
ChatRecordingService persists file-history snapshots in existing type: 'system', subtype: 'file_history_snapshot' records keyed by promptId.
SessionService.loadSession() restores the snapshot chain and deduplicates same-promptId records with last-wins semantics.
FileHistoryService.trackEdit(...) calls an optional recorder callback after it actually adds or heals a backup in the latest snapshot, fixing the last-turn-exit case where edits happened after makeSnapshot(...) but before the next turn.
Older logs without snapshot records still load with no file-history state, and malformed snapshot records are skipped with warning.
No dedicated schemaVersion or isSnapshotUpdate field was added because qwen-code kept the persisted record shape unchanged; append-only same-promptId records plus last-wins resume semantics provide the needed snapshot-update behavior.
Item B — Shell-tool change tracking
PR #4064 hooks file history into edit and write_file only. Changes made via run_shell_command (sed -i, cp, mv, rm, npm run ..., git apply, etc.) and out-of-tool manual edits are not captured.
This mostly matches claude-code scope. Upstream tracks edit/write/notebook paths plus one special simulated-sed-edit path; it does not track arbitrary shell writes.
Remaining work:
B1: port the simulated-sed-edit path from upstream. When the model emits a qualifying sed -i 's/a/b/g' file, parseSedEditCommand re-routes execution through applySedEdit (a direct file write), which lets file-history tracking capture the recognized file path.
B2: generic shell tracking decision. Keep out of scope for now because upstream punts on this and post-hoc approaches such as git status --porcelain are semantically risky: they only work in git repos, observe changes after mutation instead of before it, and can clobber in-flight backups.
Item C — GeminiClient.sendMessageStream → makeSnapshot bridge tests ✅ Done in PR #5057
Original gap: client.ts called getFileHistoryService().makeSnapshot(prompt_id) for SendMessageType.UserQuery turns, but the integration was not directly tested.
UserQuery turns record the latest snapshot when one exists.
ToolResult and Retry turns do not call makeSnapshot(...).
makeSnapshot(...) failures are swallowed and the stream still yields content.
recordFileHistorySnapshot(...) failures are swallowed and the stream still yields content.
Item D — getDiffStats concurrency limit ⏸️ Deferred
FileHistoryService.getDiffStats runs Promise.all over every entry in state.trackedFiles with no bound. Each iteration does stat + readFile (both original and backup) + diffLines. On a long-running session with many tracked large files this can spike memory and freeze the UI during the rewind selector's phase-2 diff load.
Reference: upstream claude-code/src/utils/fileHistory.ts:fileHistoryGetDiffStats has the same unbounded Promise.all pattern. Treat this as a possible qwen-code hardening task, not an upstream-parity requirement.
Deferred acceptance criteria if revived later:
Bound the parallelism to a small concurrency (for example 10) using a worker-pool helper or p-limit.
Add a regression test for bounded behavior on a large trackedFiles set.
Item E — Per-file failure reason for filesFailed ⏸️ Deferred
When /rewind reports Failed to restore N file(s): foo.ts, the user has no information about why: disk full, permission, race, missing backup, etc. The cause is in debug logging but invisible under default logging.
Reference: claude-code logs per-file restore failures and telemetry events but does not expose filesFailed reasons to users or persist a failedReason field. Keep qwen-code's current file-name-only reporting unless a separate UX/schema decision is made.
Deferred acceptance criteria if revived later:
FileHistoryBackup.failedReason?: string populated when snapshot or restore work fails.
applySnapshot(...) surfaces the reason alongside the file name in filesFailed.
Persisted schema shape is explicitly versioned if this becomes part of the stored record.
claude-code alignment notes
Local upstream reference checked in ~/Projects/claude-code on 2026-06-12.
Item A (cross-session persistence): supported upstream.claude-code records standalone JSONL entries with type: 'file-history-snapshot', keyed by the user message UUID. fileHistoryMakeSnapshot(...) records the initial snapshot for the turn, and fileHistoryTrackEdit(...) records isSnapshotUpdate: true for the same message after an edit mutates that snapshot. On resume, sessionStorage.ts builds the file-history snapshot chain in transcript order and fileHistoryRestoreStateFromLog(...) hydrates appState.fileHistory. When the session id changes, copyFileHistoryForResume(...) hard-links/copies backup files into the new session's file-history directory. Upstream does not use a dedicated file-history schemaVersion; older logs simply have no file-history-snapshot entries and load without file-history state.
Item B1 (simulated sed -i edit): supported upstream.BashTool/sedEditParser.ts recognizes simple in-place sed substitutions, the permission UI previews the exact file diff, and after approval an internal-only _simulatedSedEdit is injected. BashTool then applies the edit by direct file write and calls fileHistoryTrackEdit(...) before writing. This is the only shell-command path upstream routes through file history.
Item B2 (generic shell tracking): not supported upstream; out of scope for now. Arbitrary shell writes such as cp, mv, rm, git apply, package scripts, and manually edited files are not tracked by claude-code. We should not implement generic shell tracking in this issue unless upstream adds a concrete design later.
Item C (qwen-code GeminiClient.sendMessageStream bridge tests): qwen-specific.claude-code has no GeminiClient equivalent; snapshots are triggered from prompt-submit / query-engine paths. This is qwen-code focused test coverage for our own bridge and is now covered by PR fix(core): Persist file history snapshot updates #5057.
Item D (getDiffStats concurrency limit): not supported upstream; defer for now.claude-code/src/utils/fileHistory.ts:fileHistoryGetDiffStats still uses unbounded Promise.all over state.trackedFiles. Treat this as a possible qwen-code hardening task, not a port-alignment requirement.
Item E (per-file failure reasons): not supported upstream; defer for now.claude-code logs per-file restore failures and telemetry events but does not expose filesFailed reasons to users or persist a failedReason field. Keep qwen-code's current file-name-only failure reporting unless a separate UX/schema decision is made.
Tracks deferred follow-up items from PR #4064 (
/rewindfile restoration). All real but each was large enough to belong outside the introduction PR. Sibling follow-ups: #4173 (session backup cleanup), #4187 (RewindSelector + handleRewindConfirm tests), #4216 (TOCTOU + sticky failed-marker fixes).Current status (updated 2026-06-14)
fix(core): Persist file history snapshot updates), merged on 2026-06-13 UTC. qwen-code persistsfile_history_snapshotrecords, restores them on session load, and immediately re-records the latest snapshot whentrackEdit(...)mutates it.sed -iedit tracking: still open.claude-codesupports this specific shell path by parsing simple in-place sed substitutions and applying them through a direct file write that triggers file-history tracking.claude-codedoes not track arbitrary shell writes such ascp,mv,rm,git apply, package scripts, or manual edits.GeminiClient.sendMessageStreambridge tests: implemented by PR fix(core): Persist file history snapshot updates #5057. The tests cover UserQuery snapshot creation/recording, non-UserQuery and retry skips, and best-effort error swallowing.getDiffStatsconcurrency limit: deferred.claude-codealso uses unboundedPromise.all; this would be qwen-code hardening beyond upstream parity.claude-codelogs restore failures but does not expose or persist per-file failure reasons.Remaining active upstream-parity work: only Item B1. Generic shell tracking, diff concurrency limiting, and per-file failure reasons should stay deferred unless upstream adds a concrete design or qwen-code deliberately chooses to go beyond upstream.
Item A — Cross-session snapshot persistence ✅ Done in PR #5057
Original problem:
FileHistoryService.getSnapshots()andrestoreFromSnapshots()existed but were not wired into session persistence. After a process restart or/resume, the in-memory state was empty, so/rewindcould not see snapshots from earlier sessions even though backup files existed under~/.qwen/file-history/{prevSessionId}/.Upstream reference:
claude-code/src/utils/sessionStorage.tsbuilds aFileHistorySnapshotChainkeyed by message uuid into the session transcript;claude-code/src/utils/sessionRestore.tscallsfileHistoryRestoreStateFromLogon resume to hydrateappState.fileHistory.fileHistoryTrackEdit(...)also records an update for the current message after an edit mutates the latest snapshot.Resolved in qwen-code by PR #5057:
ChatRecordingServicepersists file-history snapshots in existingtype: 'system',subtype: 'file_history_snapshot'records keyed bypromptId.SessionService.loadSession()restores the snapshot chain and deduplicates same-promptIdrecords with last-wins semantics.FileHistoryService.trackEdit(...)calls an optional recorder callback after it actually adds or heals a backup in the latest snapshot, fixing the last-turn-exit case where edits happened aftermakeSnapshot(...)but before the next turn.schemaVersionorisSnapshotUpdatefield was added because qwen-code kept the persisted record shape unchanged; append-only same-promptIdrecords plus last-wins resume semantics provide the needed snapshot-update behavior.Item B — Shell-tool change tracking
PR #4064 hooks file history into
editandwrite_fileonly. Changes made viarun_shell_command(sed -i,cp,mv,rm,npm run ...,git apply, etc.) and out-of-tool manual edits are not captured.This mostly matches
claude-codescope. Upstream tracks edit/write/notebook paths plus one special simulated-sed-edit path; it does not track arbitrary shell writes.Remaining work:
sed -i 's/a/b/g' file,parseSedEditCommandre-routes execution throughapplySedEdit(a direct file write), which lets file-history tracking capture the recognized file path.git status --porcelainare semantically risky: they only work in git repos, observe changes after mutation instead of before it, and can clobber in-flight backups.Item C —
GeminiClient.sendMessageStream→makeSnapshotbridge tests ✅ Done in PR #5057Original gap:
client.tscalledgetFileHistoryService().makeSnapshot(prompt_id)forSendMessageType.UserQueryturns, but the integration was not directly tested.Resolved in qwen-code by PR #5057:
makeSnapshot(promptId).makeSnapshot(...).makeSnapshot(...)failures are swallowed and the stream still yields content.recordFileHistorySnapshot(...)failures are swallowed and the stream still yields content.Item D —
getDiffStatsconcurrency limit ⏸️ DeferredFileHistoryService.getDiffStatsrunsPromise.allover every entry instate.trackedFileswith no bound. Each iteration doesstat+readFile(both original and backup) +diffLines. On a long-running session with many tracked large files this can spike memory and freeze the UI during the rewind selector's phase-2 diff load.Reference: upstream
claude-code/src/utils/fileHistory.ts:fileHistoryGetDiffStatshas the same unboundedPromise.allpattern. Treat this as a possible qwen-code hardening task, not an upstream-parity requirement.Deferred acceptance criteria if revived later:
p-limit.trackedFilesset.Item E — Per-file failure reason for
filesFailed⏸️ DeferredWhen
/rewindreportsFailed to restore N file(s): foo.ts, the user has no information about why: disk full, permission, race, missing backup, etc. The cause is in debug logging but invisible under default logging.Reference:
claude-codelogs per-file restore failures and telemetry events but does not exposefilesFailedreasons to users or persist afailedReasonfield. Keep qwen-code's current file-name-only reporting unless a separate UX/schema decision is made.Deferred acceptance criteria if revived later:
FileHistoryBackup.failedReason?: stringpopulated when snapshot or restore work fails.applySnapshot(...)surfaces the reason alongside the file name infilesFailed.claude-code alignment notes
Local upstream reference checked in
~/Projects/claude-codeon 2026-06-12.claude-coderecords standalone JSONL entries withtype: 'file-history-snapshot', keyed by the user message UUID.fileHistoryMakeSnapshot(...)records the initial snapshot for the turn, andfileHistoryTrackEdit(...)recordsisSnapshotUpdate: truefor the same message after an edit mutates that snapshot. On resume,sessionStorage.tsbuilds the file-history snapshot chain in transcript order andfileHistoryRestoreStateFromLog(...)hydratesappState.fileHistory. When the session id changes,copyFileHistoryForResume(...)hard-links/copies backup files into the new session's file-history directory. Upstream does not use a dedicated file-historyschemaVersion; older logs simply have nofile-history-snapshotentries and load without file-history state.sed -iedit): supported upstream.BashTool/sedEditParser.tsrecognizes simple in-place sed substitutions, the permission UI previews the exact file diff, and after approval an internal-only_simulatedSedEditis injected.BashToolthen applies the edit by direct file write and callsfileHistoryTrackEdit(...)before writing. This is the only shell-command path upstream routes through file history.cp,mv,rm,git apply, package scripts, and manually edited files are not tracked byclaude-code. We should not implement generic shell tracking in this issue unless upstream adds a concrete design later.GeminiClient.sendMessageStreambridge tests): qwen-specific.claude-codehas noGeminiClientequivalent; snapshots are triggered from prompt-submit / query-engine paths. This is qwen-code focused test coverage for our own bridge and is now covered by PR fix(core): Persist file history snapshot updates #5057.getDiffStatsconcurrency limit): not supported upstream; defer for now.claude-code/src/utils/fileHistory.ts:fileHistoryGetDiffStatsstill uses unboundedPromise.alloverstate.trackedFiles. Treat this as a possible qwen-code hardening task, not a port-alignment requirement.claude-codelogs per-file restore failures and telemetry events but does not exposefilesFailedreasons to users or persist afailedReasonfield. Keep qwen-code's current file-name-only failure reporting unless a separate UX/schema decision is made.Related
🤖 Generated with Qwen Code