Skip to content

Seed Claude transcripts into the target project dir on fork/resume - #5943

Closed
lawrencecchen wants to merge 4 commits into
mainfrom
issue-5941-fork-resume-seed
Closed

lawrencecchen wants to merge 4 commits into
mainfrom
issue-5941-fork-resume-seed

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Forking or restoring a Claude conversation into a workspace whose cwd differs from the session's origin failed at launch with No conversation found with session ID, because claude --resume <id> only searches projects/<encoded-cwd>/ for the current working directory.

ClaudeSessionTranscriptSeeder copies the transcript (and its optional <id>/ sidecar dir) into the target cwd's project dir right before launch, inside the config root the resumed claude will actually read (the launch snapshot's captured CLAUDE_CONFIG_DIR first, then the app's env, then ~/.claude). It is wired into the shared startup-input funnel in SessionRestorableAgentSnapshot, so fork-to-tab, fork-to-split, fork-to-new-workspace, and session restore all go through one path. Copy rather than hardlink so concurrent resumes of the same id from two cwds cannot interleave appends into one inode. Best-effort: seeding failures never block the launch.

Two-commit structure: the first commit adds the failing tests (red), the second adds the fix (green).

Fixes #5941

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Touches agent resume/fork launch and writes under the user's Claude config tree, but copies are best-effort, validated session IDs, and failures do not block launch.

Overview
Fixes “No conversation found” when resuming or forking a Claude session into a workspace whose cwd differs from where the transcript was originally stored. Claude only looks under projects/<encoded-cwd>/, so the PR copies <sessionId>.jsonl (and optional sidecar dir) into that folder under the config root the resumed process will read—launch snapshot CLAUDE_CONFIG_DIR first, then process env, then ~/.claude, with legacy path preference aligned to launch policy.

ClaudeSessionTranscriptSeeder implements encoding (realpath-style /private/tmp, non-alphanumerics → -), config candidate discovery, idempotent copy (no hardlinks), UUID-shaped session id validation, and best-effort behavior that never blocks launch.

Resume/fork startup paths are centralized in new RestorableAgentSessionStartupInput.swift, which calls seeding immediately before terminal startup input for resumeStartupInput, resumeStartupCommand, and forkStartupInput. The same startup/script-store logic is removed from RestorableAgentSession.swift (file-size refactor; launcher quoting now uses TerminalStartupShellQuoting.singleQuoted).

Adds ClaudeSessionTranscriptSeederTests and Xcode project entries for the new sources.

Reviewed by Cursor Bugbot for commit d2677ec. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes failed claude --resume <id> when forking or restoring into a different cwd by seeding the transcript into the target project dir at launch. Eliminates “No conversation found” errors; resume now works across workspaces. Fixes #5941.

  • Bug Fixes

    • Added ClaudeSessionTranscriptSeeder to copy <id>.jsonl and optional <id>/ sidecar into projects/<encoded-cwd>/ in the authoritative config root (first candidate: launch env CLAUDE_CONFIG_DIR, then process env, then ~/.claude), with each candidate normalized via ClaudeConfigDirectoryPath.preferredPath. Later candidates are source fallbacks only; we write only to the first.
    • Hooked seeding into the shared startup-input funnel used by resume and fork (tab, split, new workspace, restore).
    • Matches Claude’s project encoding (realpath with /private, replace non-alphanumerics with -); uses copy instead of hardlink; best-effort: failures don’t block launch.
  • Refactors

    • Moved resume/fork startup-input methods and script store into RestorableAgentSessionStartupInput.swift to keep RestorableAgentSession.swift within size limits (no behavior change).

Written for commit d2677ec. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Automatic seeding of conversation transcripts when resuming or forking sessions, with safe validation, config-directory discovery/fallback, idempotent copying, optional sidecar preservation, and improved resume command handling including optional launcher-script behavior for return-to-login flows.
  • Tests

    • End-to-end tests covering transcript seeding, idempotency, config discovery/fallbacks, invalid-session safety, and resume/fork triggers.
  • Chores

    • Project configuration updated to include the new sources and tests.

lawrencecchen and others added 2 commits June 11, 2026 20:52
…d (red)

Forking or restoring a Claude conversation into a workspace whose cwd
differs from the session's origin fails with "No conversation found"
because claude --resume only searches projects/<encoded-cwd>/.
#5941

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
claude --resume <id> only searches projects/<encoded-cwd>/ of the
current working directory, so forking or restoring a conversation into
a different directory failed with "No conversation found".
ClaudeSessionTranscriptSeeder copies the transcript (and its optional
sidecar dir) into the target cwd's project dir at launch time, inside
the config root the resumed claude will actually read (captured
CLAUDE_CONFIG_DIR first, then process env, then ~/.claude). Wired into
the shared startup-input funnel so fork-to-tab, fork-to-split,
fork-to-new-workspace, and session restore all benefit.

Fixes #5941

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 12, 2026 11:01pm
cmux-staging Building Building Preview, Comment Jun 12, 2026 11:01pm

@coderabbitai

coderabbitai Bot commented Jun 12, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds ClaudeSessionTranscriptSeeder to copy Claude session JSONL transcripts (and optional sidecars) into the encoded project directory for a target working directory, and invokes seeding from restorable session resume/fork startup paths before generating launcher commands or inline input.

Changes

Claude Session Transcript Seeding

Layer / File(s) Summary
Seeder implementation: encoding, candidates, and seeding logic
Sources/ClaudeSessionTranscriptSeeder.swift
Implements ClaudeSessionTranscriptSeeder with encodedProjectDirName, defaultConfigDirCandidates, and seedIfNeeded. Validates UUID-shaped session IDs, resolves working directory symlinks, builds deduplicated config-dir candidates, searches for source projects/<source>/<sessionId>.jsonl, and copies JSONL plus optional sidecar into projects/<encoded-target>/ (best-effort, swallows listing/copy errors). Includes file/dir helpers and symlink-aware realpath logic.
Tests: seeding behavior, encoding, candidates, integration
cmuxTests/ClaudeSessionTranscriptSeederTests.swift
Unit tests cover encoding of working directories (non-alphanumeric replacement and /private/tmp resolution), candidate ordering/deduplication, successful copy of JSONL and subagents sidecar, idempotent behavior when target already contains the transcript, failure handling for missing/invalid session IDs, and integration asserting forkStartupInput() and resumeStartupInput() trigger seeding.
Session startup integration and project registration
Sources/RestorableAgentSession.swift, Sources/RestorableAgentSessionStartupInput.swift, cmux.xcodeproj/project.pbxproj
Adds private seedClaudeTranscriptIfNeeded(fileManager:) that computes effective target working directory and calls ClaudeSessionTranscriptSeeder.seedIfNeeded(...) before resumeStartupInput, resumeStartupCommand, and forkStartupInput. Centralizes resume/fork startup-input construction and launcher script store (AgentResumeScriptStore). Updates Xcode project to compile new source and test files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#5154: Both PRs modify the Claude fork/resume restoration flow to operate on the correct encoded Claude transcript project directory.
  • manaflow-ai/cmux#5300: Related adjustments to session-resume/fork behavior and transcript availability.
  • manaflow-ai/cmux#5242: Adjusts transcript lookup/resolution logic that interacts with seeding behavior.

Poem

🐰 I hopped through config folders, tail a-fluff,
I copied transcripts careful and soft,
Now forks and resumes roam wide and free—
Different cwds find their chat history!
Hooray, says the rabbit, with a happy puff.


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Expensive Synchronous Load ❌ Error New seeder calls sync FileManager.contentsOfDirectory (full scan of configRoot/projects) and runs via resumeStartupInput on @MainActor TerminalPanel. Move ClaudeSessionTranscriptSeeder.seedIfNeeded off the @MainActor/interactive path (e.g., Task.detached or generate a launcher script that performs the scan/copy before calling claude).
Cmux Swift File And Package Boundaries ❌ Error PR adds standalone filesystem/transcript seeding logic in Sources/ClaudeSessionTranscriptSeeder.swift (161 LOC) with dedicated XCTest, wired into the main app target (pbxproj); it should live beh... Extract ClaudeSessionTranscriptSeeder (and ClaudeSessionTranscriptSeederTests) into an appropriate SwiftPM package target (likely alongside CMUXAgentLaunch), keeping only the startup wiring in the app target.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR description comprehensively covers what changed, why it matters, and references issue #5941, but lacks testing details and demo video required by template. Add sections for 'How did you test this change?' and 'What did you verify manually?' to explain testing strategy, or clarify if testing was deferred to inline CI/review process.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Seed Claude transcripts into the target project dir on fork/resume' clearly and specifically summarizes the main change: copying transcripts to enable fork/resume across different working directories.
Linked Issues check ✅ Passed All objectives from issue #5941 are met: ClaudeSessionTranscriptSeeder copies transcripts to the target project dir, honors the config root hierarchy, centralizes seeding in startup-input flows, uses copy instead of hardlink, and ensures best-effort non-blocking behavior.
Out of Scope Changes check ✅ Passed All changes are scoped to the transcript seeding feature: new seeder implementation, integration into startup flows, test coverage, project file updates, and refactoring RestorableAgentSession for file size without behavioral changes.
Cmux Swift Actor Isolation ✅ Passed New Swift files add synchronous transcript-copy helpers and startup-input wiring with no @MainActor usage or new Sendable/UI-bound types; actor-isolation issues not introduced or worsened.
Cmux Swift Blocking Runtime ✅ Passed Checked new/changed Swift files for swift-blocking-runtime primitives (Task.sleep, DispatchSemaphore/waits, main sync, timers, asyncAfter, locks, polling waits); none found.
Cmux Cache Substitution Correctness ✅ Passed Reviewed new/changed Swift files for persistence/history/undo/snapshot cache swaps; Claude transcript seeding uses fresh env/FS reads + copy, with no authoritative→cached/opportunistic substitution...
Cmux No Hacky Sleeps ✅ Passed git diff --name-only is empty (no changed non-Swift runtime/build scripts); only untracked .coderabbit.swiftlint.yml. No hacky sleeps introduced per rule.
Cmux Algorithmic Complexity ✅ Passed PASS: Claude seeding is O(P) linear scan over projects with fixed <=3 config roots (defaultConfigDirCandidates adds 3) and early return on first hit; script pruning is a single linear directory lis...
Cmux Swift Concurrency ✅ Passed Seeder/startup code added uses synchronous FileManager work only; no DispatchQueue/Task/Combine/completion-handler APIs were introduced. RestorableAgentSession.swift shows 0 additions in this PR diff.
Cmux Swift @Concurrent ✅ Passed Scanned the PR’s added/edited Swift files; found 0 occurrences of nonisolated async and 0 of @concurrent, and new seeding helpers are synchronous file work.
Cmux Swift Logging ✅ Passed Reviewed PR-added/modified Swift files (seeder, startup input, restorable session, tests) for print/debugPrint/dump/NSLog/Logger usage; none found, so logging rules aren’t violated.
Cmux User-Facing Error Privacy ✅ Passed New seeding code is best-effort (catches/swallow failures) and adds no user-facing errors/alerts/API error bodies. Scanned added prod Swift files for 'throw/NSLocalized/No conversation found'—only...
Cmux Full Internationalization ✅ Passed Added Swift files only use literal path/env/script tokens and developer comments; no Swift UI/alert/error text via String(localized:/NSLocalizedString) and no web/messages/i18n changes detected.
Cmux Swiftui State Layout ✅ Passed PR only adds/updates Swift session seeding + startup-input logic; diff contains no SwiftUI state-layout markers (no import SwiftUI/@Observable/@Published/GeometryReader/@State).
Cmux Architecture Rethink ✅ Passed PR adds ClaudeSessionTranscriptSeeder and seeds via a shared helper; scans of changed Swift files show no sleeps/asyncAfter/polling/locks/observers, and RestorableAgentSession.swift seeding moved i...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Scanned the PR’s added/modified Swift files for NSWindow/NSPanel/NSWindowController/WindowGroup/cmuxAuxiliaryWindowIdentifiers/closeShortcut; found no hits, so no auxiliary window close-shortcut sh...
Cmux Source Artifacts ✅ Passed Diff vs origin/main shows only 3 added files (ClaudeSessionTranscriptSeeder.swift, RestorableAgentSessionStartupInput.swift, ClaudeSessionTranscriptSeederTests.swift) and no added/changed paths mat...
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-5941-fork-resume-seed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6cf532c. Configure here.

Comment thread Sources/ClaudeSessionTranscriptSeeder.swift
Comment thread Sources/ClaudeSessionTranscriptSeeder.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6cf532c0cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +17 to +19
return String(resolved.map { character in
character.isASCII && (character.isLetter || character.isNumber) ? character : "-"
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the existing Claude project-dir encoder

For target cwd paths containing other non-alphanumeric characters such as underscores or spaces, this new encoder seeds the transcript under a different directory than the rest of cmux still treats as Claude's project directory (RestorableAgentSessionIndex.encodeClaudeProjectDir and its tests only replace / and .). In that case the startup command still cds into the target cwd, but the copied transcript is placed in the mismatched projects/<new-encoding>/ bucket, so claude --resume can continue to report no conversation found. Please share one encoder/contract (or update all call sites/tests together) so the seed location matches the resume lookup location.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jun 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds ClaudeSessionTranscriptSeeder to fix "No conversation found" failures when forking or restoring a Claude session into a workspace whose cwd differs from the session's origin. The seeder copies <id>.jsonl (and its optional sidecar dir) into the correct projects/<encoded-cwd>/ under the authoritative config root before claude --resume launches, and is wired into all three entry points (forkStartupInput, resumeStartupInput, resumeStartupCommand).

  • New ClaudeSessionTranscriptSeeder: resolves config-dir candidates with the launch-env CLAUDE_CONFIG_DIR taking precedence, always writes into candidates[0] (the root the resumed process reads), and guards with isPlausibleSessionId to prevent path traversal. Copy rather than hardlink prevents concurrent-resume inode interleaving.
  • New RestorableAgentSessionStartupInput.swift: moves the startup-input funnel and AgentResumeScriptStore out of RestorableAgentSession.swift (size hygiene) and adds the seedClaudeTranscriptIfNeeded call in one shared place so all fork/restore paths go through a single side-effect point.
  • Tests: cover idempotency, cross-config-dir copy landing in the authoritative root, legacy-path remapping, dedup, path-traversal rejection, and integration with the fork/resume entry points.

Confidence Score: 4/5

Safe to merge after addressing the stale-snapshot selection bug; all other paths are well-covered by tests and the cross-configDir write is correctly fixed.

The source-selection loop in seedIfNeeded returns the first .jsonl found in contentsOfDirectory order, which is non-deterministic. After a session is forked once (creating a snapshot in project dir B), and the original session in dir A continues accumulating turns, a subsequent fork to dir C can pick up the stale snapshot from B instead of the fresh transcript from A. The resumed Claude then silently starts from an earlier point in the conversation with no indication that turns are missing.

Sources/ClaudeSessionTranscriptSeeder.swift — the inner project-dir scan loop (lines 87–113) needs to prefer the most recently modified transcript when multiple project dirs contain the same session ID.

Important Files Changed

Filename Overview
Sources/ClaudeSessionTranscriptSeeder.swift New seeder correctly copies transcript to the authoritative (first-candidate) config dir; the source-selection loop is non-deterministic when the same session ID has been seeded to multiple project dirs, risking a stale transcript being used for resume.
Sources/RestorableAgentSessionStartupInput.swift Moved startup-input logic from RestorableAgentSession.swift and added seedClaudeTranscriptIfNeeded call in the three launch entry points; refactor is clean, no behavior change to existing plumbing other than the seeding addition.
Sources/RestorableAgentSession.swift Removal-only change: startup-input methods and AgentResumeScriptStore moved to RestorableAgentSessionStartupInput.swift; no logic changes.
cmuxTests/ClaudeSessionTranscriptSeederTests.swift Solid coverage of idempotency, cross-config-dir copy, path encoding, dedup, and integration with forkStartupInput/resumeStartupInput; missing a test for the multi-copy non-deterministic selection scenario.
cmux.xcodeproj/project.pbxproj Mechanical addition of the two new source files and one test file to the correct build targets.

Reviews (3): Last reviewed commit: "Seed into the authoritative config root ..." | Re-trigger Greptile

Comment on lines +65 to +93
for configDir in configDirCandidates {
let projects = configDir.appendingPathComponent("projects")
let targetDir = projects.appendingPathComponent(targetName)
let targetTranscript = targetDir.appendingPathComponent(transcriptName)
if fileManager.fileExists(atPath: targetTranscript.path) {
return true
}
guard let projectDirs = try? fileManager.contentsOfDirectory(
at: projects, includingPropertiesForKeys: nil) else {
continue
}
for projectDir in projectDirs where projectDir.lastPathComponent != targetName {
let sourceTranscript = projectDir.appendingPathComponent(transcriptName)
guard isFile(at: sourceTranscript, fileManager: fileManager) else { continue }
do {
try fileManager.createDirectory(at: targetDir, withIntermediateDirectories: true)
try fileManager.copyItem(at: sourceTranscript, to: targetTranscript)
} catch {
continue
}
let sidecar = projectDir.appendingPathComponent(sessionId)
let sidecarTarget = targetDir.appendingPathComponent(sessionId)
if isDirectory(at: sidecar, fileManager: fileManager),
!fileManager.fileExists(atPath: sidecarTarget.path) {
try? fileManager.copyItem(at: sidecar, to: sidecarTarget)
}
return true
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cross-configDir copy: transcript lands in the wrong config dir

seedIfNeeded iterates candidates, finds the source transcript in configDir[i], and copies it within that same dir. But the resumed Claude process reads from configDirCandidates[0] (the most-specific dir). When these two differ, the copy is placed where Claude will never look and the launch still hits "No conversation found."

Concrete failure: original session used the default ~/.claude (no CLAUDE_CONFIG_DIR in launchEnvironment), but the current cmux process has CLAUDE_CONFIG_DIR=/other in its environment. Candidates become ["/other", "~/.claude"]. The outer loop finds no transcript in /other, then finds the source in ~/.claude and copies it to ~/.claude/projects/<targetName>/. The resumed claude inherits the process environment, uses /other, and still fails.

The fix is to copy into configDirCandidates[0] (or the first writable candidate) regardless of which candidate the source was discovered in.

Comment on lines +65 to +94
for configDir in configDirCandidates {
let projects = configDir.appendingPathComponent("projects")
let targetDir = projects.appendingPathComponent(targetName)
let targetTranscript = targetDir.appendingPathComponent(transcriptName)
if fileManager.fileExists(atPath: targetTranscript.path) {
return true
}
guard let projectDirs = try? fileManager.contentsOfDirectory(
at: projects, includingPropertiesForKeys: nil) else {
continue
}
for projectDir in projectDirs where projectDir.lastPathComponent != targetName {
let sourceTranscript = projectDir.appendingPathComponent(transcriptName)
guard isFile(at: sourceTranscript, fileManager: fileManager) else { continue }
do {
try fileManager.createDirectory(at: targetDir, withIntermediateDirectories: true)
try fileManager.copyItem(at: sourceTranscript, to: targetTranscript)
} catch {
continue
}
let sidecar = projectDir.appendingPathComponent(sessionId)
let sidecarTarget = targetDir.appendingPathComponent(sessionId)
if isDirectory(at: sidecar, fileManager: fileManager),
!fileManager.fileExists(atPath: sidecarTarget.path) {
try? fileManager.copyItem(at: sidecar, to: sidecarTarget)
}
return true
}
}
return false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Synchronous O(N) project-dir scan on interactive path

contentsOfDirectory lists every entry under projects/, then isFile stat-checks each one, all synchronously inside seedIfNeeded. This is called from forkStartupInput and resumeStartupInput, which are invoked on the main-actor path in Workspace.forkAgentConversation (fork-to-tab, fork-to-split, fork-to-new-workspace) and restoreSessionSnapshot. For a user with hundreds of sessions the scan adds visible latency to every fork and restore. Consider moving the seeding call into an async or off-main helper so the I/O doesn't block the UI thread.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Keeps the file inside its length budget: the resume/fork startup-input
methods and AgentResumeScriptStore move to
RestorableAgentSessionStartupInput.swift unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ede8bd7880

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +65 to +68
for configDir in configDirCandidates {
let projects = configDir.appendingPathComponent("projects")
let targetDir = projects.appendingPathComponent(targetName)
let targetTranscript = targetDir.appendingPathComponent(transcriptName)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Seed the configured Claude root, not a fallback root

When a snapshot carries CLAUDE_CONFIG_DIR, the resume command will launch Claude with that configured root, but this loop treats every candidate as both a possible source and the destination. If the configured root is empty/stale while the transcript is only found in a later fallback such as ~/.claude or the process env, seedIfNeeded copies into that fallback target and returns true, while the resumed claude --resume still reads the configured root and reports no conversation found. Please keep the active launch config root as the destination and use fallbacks only as sources, or only return success when the transcript is present in the launch root.

Useful? React with 👍 / 👎.

Review fixes (Bugbot high/medium, Greptile P1): the first candidate is
now the only root written to, later candidates are source-search
fallbacks, and every candidate goes through
ClaudeConfigDirectoryPath.preferredPath so seeding targets the same
root the launch environment policy hands the resumed claude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d2677ece3e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +87 to +94
for configDir in configDirCandidates {
let projects = configDir.appendingPathComponent("projects")
guard let projectDirs = try? fileManager.contentsOfDirectory(
at: projects, includingPropertiesForKeys: nil) else {
continue
}
for projectDir in projectDirs {
let sourceTranscript = projectDir.appendingPathComponent(transcriptName)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Choose the latest duplicate transcript when seeding

After this copy-based seeding runs once, the same Claude session id can exist in multiple projects/<cwd>/ directories; if the user continues the resumed conversation in the new cwd and later forks/restores it into a third cwd, this loop copies the first matching <session>.jsonl returned by directory enumeration rather than the most recently updated transcript. In that scenario the new pane can resume from a stale copy and miss the latest conversation turns, so the seeder should select the newest/source-authoritative transcript instead of returning on the first match.

Useful? React with 👍 / 👎.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Review findings addressed in d2677ec:

  • Bugbot high (config dir path mismatch) and Greptile P1 (cross-configDir copy): every candidate now goes through ClaudeConfigDirectoryPath.preferredPath, matching what AgentLaunchEnvironmentPolicy hands the resumed claude.
  • Bugbot medium (wrong-config early success): the first candidate is now the only root checked for early success and the only root written to; later candidates are source-search fallbacks.
  • Codex P2 (reuse encodeClaudeProjectDir): kept the new encoder deliberately. Ground-truth check on claude 2.1.175: a session started in /tmp/seed_enc.test_x lands in projects/-private-tmp-seed-enc-test-x, so underscores are also replaced and /tmp resolves to /private/tmp. The existing encodeClaudeProjectDir only maps / and . and would mis-encode such paths; converging the hibernation lookup onto the stricter encoder is a separate change.
  • Greptile P2 (O(N) scan): accepted as-is. It is one directory listing plus one stat per project dir, only on resume/fork launches, and only when the target project dir lacks the transcript.

ClaudeSessionTranscriptSeederTests (10 tests) verified green on the AWS M4 Pro runner at d2677ec; the ci.yml tests job on GitHub runners only executed a subset of cmuxTests this run, so the suite was validated out-of-band.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/ClaudeSessionTranscriptSeeder.swift (1)

16-21: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Fix inconsistent Claude project-dir encoding between seeding and restore lookup

  • ClaudeSessionTranscriptSeeder.encodedProjectDirName(forWorkingDirectory:) replaces every non-alphanumeric ASCII character with - (e.g. repo_a.b → repo-a-b).
  • RestorableAgentSessionIndex.encodeClaudeProjectDir(_:) only replaces / and . with - (leaves _), so restore probes a different projects/<encoded-cwd>/ directory for the same cwd (e.g. repo_a.b → repo_a-b).
  • Use a single shared encoding helper for both seeding and restore lookup, and add a regression test that verifies restore finds the seeded transcript when the cwd contains _/other non-alphanumerics.

critical_fixes_required

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/ClaudeSessionTranscriptSeeder.swift` around lines 16 - 21, Replace
the two differing encoders with a single shared helper and update both
ClaudeSessionTranscriptSeeder.encodedProjectDirName(forWorkingDirectory:) and
RestorableAgentSessionIndex.encodeClaudeProjectDir(_:) to call it; implement the
helper (e.g., encodeClaudeProjectDirName(_:) in a common util/extension) to
perform the canonical replacement logic used for project dir names (replace "/"
and "." with "-" while preserving "_" and other characters per the intended
behavior), update both callers to use the helper, and add a regression test that
seeds a transcript then attempts restore with a working directory containing "_"
and other non-alphanumerics to assert the seeded transcript is found.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Sources/ClaudeSessionTranscriptSeeder.swift`:
- Around line 16-21: Replace the two differing encoders with a single shared
helper and update both
ClaudeSessionTranscriptSeeder.encodedProjectDirName(forWorkingDirectory:) and
RestorableAgentSessionIndex.encodeClaudeProjectDir(_:) to call it; implement the
helper (e.g., encodeClaudeProjectDirName(_:) in a common util/extension) to
perform the canonical replacement logic used for project dir names (replace "/"
and "." with "-" while preserving "_" and other characters per the intended
behavior), update both callers to use the helper, and add a regression test that
seeds a transcript then attempts restore with a working directory containing "_"
and other non-alphanumerics to assert the seeded transcript is found.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d3405aac-1685-489d-9350-f85d6b6b0a97

📥 Commits

Reviewing files that changed from the base of the PR and between ede8bd7 and d2677ec.

📒 Files selected for processing (2)
  • Sources/ClaudeSessionTranscriptSeeder.swift
  • cmuxTests/ClaudeSessionTranscriptSeederTests.swift

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — d2677ece Deployed Jun 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Forking a Claude session into a different cwd fails: "No conversation found" (claude --resume is project-scoped)

2 participants