Skip to content

Retry Codex resume lock failures and preserve cwd - #5611

Closed
austinywang wants to merge 18 commits into
mainfrom
issue-5557-codex-resume-lock-retry-cwd
Closed

austinywang wants to merge 18 commits into
mainfrom
issue-5557-codex-resume-lock-retry-cwd

Conversation

@austinywang

@austinywang austinywang commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #5557

Summary

  • add a shared CMUXAgentLaunch resume-shell builder with a bounded Codex SQLite-lock retry policy
  • retry only Codex lock signatures (database is locked / another Codex process is using its local data) with a short staggered backoff before surfacing the failure
  • keep the visible shell in the saved session working directory after the resumed agent exits or fails
  • wire the shared launcher through local agent restore, surface-resume bindings, and the codex-teams CLI startup script

Related: #5391, #5271, #4256, #4963, #4150

Tests

  • swift test --package-path Packages/CMUXAgentLaunch

Note: app-target unit tests are left to CI per repo instructions. The first commit adds the red regression coverage; the second commit makes it pass.


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


Summary by cubic

Adds bounded, startup-window retries for Codex resumes when SQLite lock signatures appear, and keeps the terminal in the session’s working directory after the agent exits. Consolidates a zsh launcher in CMUXAgentLaunch used for app restore, surface resume, and the codex-teams CLI with safer quoting and login-shell reentry.

  • New Features

    • Retry Codex resumes only when early startup output matches lock signatures, up to 3 attempts with staggered backoff; configurable via CMUX_AGENT_RESUME_RETRY_LIMIT, CMUX_AGENT_RESUME_RETRY_DELAY_SECONDS, and CMUX_AGENT_RESUME_RETRY_STARTUP_SECONDS. Auto-enabled for codex and codexTeams.
    • Shared zsh launcher (AgentResumeShellScriptBuilder) now powers local restore, surface resume, and the codex-teams startup script, preserving cwd and reentering a login shell. Adds robust shell quoting (including non-ASCII) and switches the codex-teams script to .zsh.
  • Bug Fixes

    • Avoid recording interactive transcripts during retry; capture only a bounded slice of startup output via a FIFO, and skip capture when FIFOs are unavailable.
    • Remove temporary retry logs between attempts to prevent stale matches.
    • Always return startup input: generate a retry launcher when enabled, or fall back to the inline command when scripts are disabled, cannot be created, or when inline fits the limit.

Written for commit 0c94af5. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added configurable agent resume retry support for transient startup failures, including Codex-specific lock handling.
    • Resume launch now generates zsh-compatible startup scripts and uses safer shell quoting for command payloads.
  • Bug Fixes
    • Improved session resume behavior to retry reliably while preserving the intended working directory; also ensures fallback to the inline command when script-based retry isn’t possible.
  • Tests
    • Added unit and end-to-end coverage for retry matching, shell quoting, and launcher generation.
  • Documentation
    • Updated the CLAUDE.md example output formatting.

@vercel

vercel Bot commented Jun 8, 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 18, 2026 8:54pm
cmux-staging Building Building Preview, Comment Jun 18, 2026 8:54pm

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Threads AgentResumeRetryPolicy through resume startup paths, adds zsh launcher-script generation that preserves the session working directory and optionally retries transient failures (Codex DB locks), introduces shell-quoting and script-builder utilities, and adds unit plus end-to-end tests.

Changes

Agent resume retry mechanism and working directory restoration

Layer / File(s) Summary
Retry policy framework and Codex lock detection
Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeRetryPolicy.swift, Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentResumeRetryPolicyTests.swift
Adds AgentResumeRetryPolicy with maximumRetries, delaySeconds, outputNeedles, and startupFailureWindowSeconds; predefined .disabled and .codexStateDatabaseLock presets; policy(agentKind:launcher:) selection; matches(output:) and shellGrepPattern matching; unit tests covering matching behavior, empty-needle filtering, and policy enablement by agent/launcher configuration.
Shell-safe quoting for script generation
Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeShellQuoting.swift, Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentResumeShellQuotingTests.swift
Adds AgentResumeShellQuoting.singleQuoted(_:) with ASCII single-quote escaping for ASCII-only strings and $(printf ...) octal substitution for UTF-8 containing non-ASCII bytes; tests verify both quoting behaviors.
Launcher script builder with retry and working directory restoration
Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeShellScriptBuilder.swift
Adds AgentResumeShellScriptBuilder.commandThenReturnLines(...) to emit zsh launcher lines with optional cd into workingDirectory, zsh integration re-entry conditional handling, and optional bounded retry loop using /usr/bin/script capture and grep-style pattern matching driven by AgentResumeRetryPolicy; includes plainCommandLines, retryingCommandLines, and zshIntegrationReentryLines helpers.
Script builder unit and integration tests
Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentResumeShellScriptBuilderTests.swift
Unit tests for disabled vs Codex retry output content structure; RetryLauncherHarness scaffolds isolated temp environment with fake codex executable, persistent attempt counter, and fallback logging shell; end-to-end assertions validate retry-to-success on attempt 2, retry-bound termination at attempt 4, success markers, exit status, and working-directory preservation.
SessionPersistence launcher script and retry policy threading
Sources/SessionPersistence.swift
Imports CMUXAgentLaunch; computes retry policy per surface binding kind; uses retryPolicy.isEnabled when choosing launcher-script vs inline startup input; refactors TerminalStartupReturnShellScript.commandThenReturnLines to accept retryPolicy parameter and delegate to AgentResumeShellScriptBuilder; SurfaceResumeBindingScriptStore.writeLauncherScript adds retryPolicy parameter and updates wrapping condition to use returnToLoginShell || retryPolicy.isEnabled.
RestorableAgentSnapshot resume path integration
Sources/RestorableAgentSession.swift
Resume paths compute AgentResumeRetryPolicy from snapshot kind and launch command's launcher; thread it through startupInput and AgentResumeScriptStore.writeLauncherScript; startupInput accepts retryPolicy parameter, relaxes inline/script gating when retry is enabled, and sets returnToLoginShell from retryPolicy.isEnabled.
Codex CLI startup script generation
CLI/cmux.swift
codexTeamsStartupScript now emits a .zsh launcher with #!/bin/zsh shebang, computes trimmedCwd from working directory, builds conditional launchCommand with optional cd into quoted working directory, and delegates launcher-line generation to AgentResumeShellScriptBuilder with .codexStateDatabaseLock retry policy.
End-to-end Codex resume retry tests
cmuxTests/SessionPersistenceTests.swift
Adds test harness and regression tests simulating transient and perpetual Codex DB lock scenarios; validates retry-to-success with correct attempt count and success marker emission, retry-bound termination without success marker, and working-directory preservation for launcher and surface binding paths; includes fallback-inline tests for when launcher scripts cannot be written.

Sequence Diagram(s)

sequenceDiagram
  participant Snapshot as RestorableAgentSnapshot
  participant ScriptStore as AgentResumeScriptStore
  participant Builder as AgentResumeShellScriptBuilder
  participant Policy as AgentResumeRetryPolicy
  Snapshot->>Policy: policy(kind, launcher)
  Snapshot->>ScriptStore: writeLauncherScript(..., retryPolicy)
  ScriptStore->>Builder: commandThenReturnLines(command, workingDirectory, retryPolicy)
  alt retryPolicy.isEnabled
    Builder->>Builder: emit retry loop with /usr/bin/script capture
    Builder->>Builder: grep log against policy needles
    Builder->>Builder: break on success or mismatch
  else
    Builder->>Builder: emit plain child-shell invoke
  end
  Builder->>Builder: cd to workingDirectory
  Builder->>Builder: exec -l into resume shell
  Builder-->>ScriptStore: launcher script lines
  ScriptStore-->>Snapshot: launcher script written
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#5312: Both PRs modify the shared "resume return-shell" script generation path by evolving TerminalStartupReturnShellScript.commandThenReturnLines to account for working-directory restoration and adding new retryPolicy/AgentResumeShellScriptBuilder support.
  • manaflow-ai/cmux#4777: Prior refactor of resume launcher-script generation that this PR continues by introducing the script builder and threading retry policies through the launcher creation pipeline.
  • manaflow-ai/cmux#4683: Both PRs change resume/startup shell-command generation to correctly quote/encode non-ASCII paths via AgentResumeShellQuoting, overlapping on the non-ASCII quoting logic used in zsh resume-script builders.

Poem

🐰 I hopped through shells and dodged a lock,
I wrapped each path in single-quote stock.
With retries tuned and cwd held tight,
Resume will try again by morning light.
Hooray—your shell stays where it's right! 🥕


Important

Pre-merge checks failed

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

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Expensive Synchronous Load ❌ Error The PR adds synchronous file I/O (create directory, set attributes, list/delete files, write) via AgentResumeScriptStore.writeLauncherScript() onto the @MainActor Workspace.resumeAgentHibernation()... Move script writing to a Task.detached background operation or use a cached off-main generator like SharedLiveScriptBuilder.shared instead of synchronous writeLauncherScript() calls on @MainActor paths.
Cmux Architecture Rethink ❌ Error Code correctness bug: FIFO capture fallback broken. When mkfifo fails, _cmux_resume_script_output stays /dev/null, so retry pattern matching fails and lock errors aren't retried as designed. Initialize _cmux_resume_script_output to _cmux_resume_log (not /dev/null) so output is captured and grep pattern matching works when mkfifo is unavailable.
Docstring Coverage ⚠️ Warning Docstring coverage is 5.66% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (18 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: implementing retry logic for Codex resume lock failures and preserving the current working directory.
Linked Issues check ✅ Passed All coding requirements from issue #5557 are met: bounded retry mechanism for Codex lock signatures, session working directory preservation, retry suppression via script wrapping, and comprehensive test coverage for retry behavior and cwd preservation.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the retry mechanism and cwd preservation objectives. The CLAUDE.md formatting update is incidental and not out-of-scope.
Cmux Swift Actor Isolation ✅ Passed All new Swift types (AgentResumeRetryPolicy, AgentResumeShellScriptBuilder, AgentResumeShellQuoting) are pure Sendable value types with only immutable properties; modified helper functions properly...
Cmux Swift Blocking Runtime ✅ Passed No Swift-level blocking primitives (Task.sleep, Thread.sleep, semaphores, etc.) introduced. Shell sleep commands appear only in generated shell script strings executed as subprocesses, not in Swift...
Cmux Cache Substitution Correctness ✅ Passed The PR uses persisted snapshot values (kind and launcher) as intended for resume operations. The primary agentKind is authoritative and always present; launcher is an optional hint. No fres...
Cmux No Hacky Sleeps ✅ Passed Sleeps in AgentResumeShellScriptBuilder implement dedicated, bounded retry logic with tests. Sleep is overridable via environment variables (CMUX_AGENT_RESUME_RETRY_DELAY_SECONDS=0), scoped to know...
Cmux Algorithmic Complexity ✅ Passed All new code uses fixed-size collections (outputNeedles ≤2 items) and bounded operations; no nested scans over scalable workspaces/sessions or per-collection rescans detected.
Cmux Swift Concurrency ✅ Passed No legacy async patterns introduced. New types (AgentResumeRetryPolicy, AgentResumeShellQuoting, AgentResumeShellScriptBuilder) are Sendable, synchronous, and avoid DispatchQueue/Combine/completion...
Cmux Swift @Concurrent ✅ Passed PR introduces only synchronous helper functions for shell script building and retry policy management. No nonisolated async functions, no @concurrent annotations on synchronous functions, and no co...
Cmux Swift File And Package Boundaries ✅ Passed New package types (AgentResumeRetryPolicy, AgentResumeShellQuoting, AgentResumeShellScriptBuilder) are properly isolated in CMUXAgentLaunch SwiftPM package with single, focused responsibilities (<1...
Cmux Swift Logging ✅ Passed No Swift logging violations found. CLI print statements are user-facing output (allowed). New production code contains no print/debugPrint/dump/NSLog. File writes are for shell script generation only.
Cmux User-Facing Error Privacy ✅ Passed PR adds internal retry logic for Codex lock errors with no user-facing error messages, alerts, or sensitive data exposure; error signatures are used only for internal pattern matching against captu...
Cmux Full Internationalization ✅ Passed PR adds only internal backend utilities (retry policies, shell quoting, script builders) with no user-facing text. Tests and developer docs are exempt per i18n rules.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state changes detected. PR adds Foundation-only utilities, domain models, and CLI logic with no ObservableObject, @Published, @State, @Observable, GeometryReader, or lazy-list patterns.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR contains no user-visible NSWindow, NSPanel, NSWindowController, SwiftUI Window, or WindowGroup code. New files are shell/retry utilities; modified files lack window UI additions.
Cmux Source Artifacts ✅ Passed All 11 changed files are legitimate source code, tests, or documentation with no build artifacts, generated logs, cache files, or other source control violations.
Description check ✅ Passed The pull request description is comprehensive and complete, covering all required template sections with clear explanations of changes, rationale, and testing approach.
✨ 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-5557-codex-resume-lock-retry-cwd

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.

@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.

Actionable comments posted: 3

Caution

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

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

397-421: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fall back to the inline command if the retry wrapper can't be written.

retryPolicy.isEnabled now routes even short Codex bindings through SurfaceResumeBindingScriptStore, but this path returns nil when the temp script write fails. That regresses from "resume without retries/cwd restoration" to "can't resume at all" even though inlineInput already fits under maxInlineStartupInputBytes.

Suggested fix
         guard let scriptURL = SurfaceResumeBindingScriptStore.writeLauncherScript(
             inlineInput: inlineInput,
             binding: self,
             fileManager: fileManager,
             temporaryDirectory: temporaryDirectory,
             returnToLoginShell: retryPolicy.isEnabled,
             retryPolicy: retryPolicy
         ) else {
-            return nil
+            return inlineInput.utf8.count <= Self.maxInlineStartupInputBytes ? inlineInput : nil
         }
🤖 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/SessionPersistence.swift` around lines 397 - 421, In
startupInputWithLauncherScript, when
SurfaceResumeBindingScriptStore.writeLauncherScript(...) returns nil you must
fall back to inlineInput if it fits under Self.maxInlineStartupInputBytes (so
short inputs still resume), otherwise return nil; update the guard/else around
scriptURL in startupInputWithLauncherScript to return inlineInput when
inlineInput.utf8.count <= Self.maxInlineStartupInputBytes and return nil only
when the inline input is too large.
Sources/RestorableAgentSession.swift (1)

761-797: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the inline fallback contract when the retry wrapper is unavailable.

With retryPolicy.isEnabled, this helper now bypasses both fallback guards: disabling launcher scripts returns oversized inline input, and a temp-script write failure returns nil even when the original command still fits inline. That makes the new retry path more fragile than the pre-retry behavior.

Suggested fix
         guard retryPolicy.isEnabled || !allowOversizedInlineInput else {
             return inlineInput
         }
         guard allowLauncherScript else {
-            return retryPolicy.isEnabled ? inlineInput : nil
+            if inlineInput.utf8.count <= Self.maxInlineStartupInputBytes || allowOversizedInlineInput {
+                return inlineInput
+            }
+            return nil
         }
         guard let scriptURL = AgentResumeScriptStore.writeLauncherScript(
             command: command,
             kind: kind,
             sessionId: sessionId,
@@
             workingDirectory: registration?.cwd == .ignore
                 ? nil
                 : (workingDirectory ?? launchCommand?.workingDirectory)
         ) else {
-            return nil
+            if inlineInput.utf8.count <= Self.maxInlineStartupInputBytes || allowOversizedInlineInput {
+                return inlineInput
+            }
+            return nil
         }
🤖 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/RestorableAgentSession.swift` around lines 761 - 797, The
startupInput logic currently lets retryPolicy.isEnabled bypass the inline-size
and launcher-script guards, causing oversized or missing-script fallbacks to
behave incorrectly; update startupInput so that retryPolicy only enables use of
a launcher wrapper but does not disable the original inline fallback contract:
keep the inlineInput size check (inlineInput.utf8.count <=
Self.maxInlineStartupInputBytes) as the default condition for returning
inlineInput, only allow creating/using a launcher script when inlineInput is too
large and allowLauncherScript is true, and if
AgentResumeScriptStore.writeLauncherScript returns nil fallback to returning
inlineInput only when inlineInput.utf8.count <= Self.maxInlineStartupInputBytes
(otherwise return nil); reference startupInput, Self.maxInlineStartupInputBytes,
retryPolicy.isEnabled, allowLauncherScript, inlineInput, and
AgentResumeScriptStore.writeLauncherScript when making the changes.
🤖 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.

Inline comments:
In
`@Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeRetryPolicy.swift`:
- Around line 65-68: The doc comment for the public function matches(output:) is
missing a "- Returns:" DocC callout; update the comment block above public func
matches(output: String) -> Bool to include a "- Returns:" line that clearly
states what the Bool represents (e.g., true when the combined stdout/stderr
contains a retryable signature, false otherwise), keeping the existing "-
Parameter output:" and overall formatting consistent with package public API
guidelines.
- Around line 24-27: The initializer public init(maximumRetries: Int,
delaySeconds: Double, outputNeedles: [String]) currently assigns outputNeedles
directly which allows empty or whitespace-only strings (e.g. [""]) so
matches(output:) will always succeed; update the init to normalize outputNeedles
by trimming whitespace from each string and filtering out any resulting empty
strings (e.g. outputNeedles.map { $0.trimmingCharacters(...) }.filter {
!$0.isEmpty }) before assigning to self.outputNeedles so isEnabled and
matches(output:) behave correctly.

In
`@Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeShellQuoting.swift`:
- Around line 4-16: Add targeted unit tests for singleQuoted(_:) and
asciiPrintfCommandSubstitution(for:) that assert exact output for both branches:
(1) ASCII-only input including embedded single quotes and newlines (verify the
returned string equals "'" + value.replacingOccurrences(of: "'", with: "'\\''")
+ "'"), and (2) non-ASCII input (e.g., emoji, accented characters, multi-byte
UTF‑8) which must exercise asciiPrintfCommandSubstitution(for:) and assert the
returned string equals the literal "$(printf '<octal-bytes>')" form produced by
mapping value.utf8 to \%03o octal sequences; include explicit expected strings
in assertions so any regression in quoting or octal encoding fails the tests.

---

Outside diff comments:
In `@Sources/RestorableAgentSession.swift`:
- Around line 761-797: The startupInput logic currently lets
retryPolicy.isEnabled bypass the inline-size and launcher-script guards, causing
oversized or missing-script fallbacks to behave incorrectly; update startupInput
so that retryPolicy only enables use of a launcher wrapper but does not disable
the original inline fallback contract: keep the inlineInput size check
(inlineInput.utf8.count <= Self.maxInlineStartupInputBytes) as the default
condition for returning inlineInput, only allow creating/using a launcher script
when inlineInput is too large and allowLauncherScript is true, and if
AgentResumeScriptStore.writeLauncherScript returns nil fallback to returning
inlineInput only when inlineInput.utf8.count <= Self.maxInlineStartupInputBytes
(otherwise return nil); reference startupInput, Self.maxInlineStartupInputBytes,
retryPolicy.isEnabled, allowLauncherScript, inlineInput, and
AgentResumeScriptStore.writeLauncherScript when making the changes.

In `@Sources/SessionPersistence.swift`:
- Around line 397-421: In startupInputWithLauncherScript, when
SurfaceResumeBindingScriptStore.writeLauncherScript(...) returns nil you must
fall back to inlineInput if it fits under Self.maxInlineStartupInputBytes (so
short inputs still resume), otherwise return nil; update the guard/else around
scriptURL in startupInputWithLauncherScript to return inlineInput when
inlineInput.utf8.count <= Self.maxInlineStartupInputBytes and return nil only
when the inline input is too large.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2ca085c7-50b3-4623-8c1b-aba2fcb2a519

📥 Commits

Reviewing files that changed from the base of the PR and between d97d513 and 560eea9.

📒 Files selected for processing (9)
  • CLI/cmux.swift
  • Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeRetryPolicy.swift
  • Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeShellQuoting.swift
  • Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeShellScriptBuilder.swift
  • Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentResumeRetryPolicyTests.swift
  • Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentResumeShellScriptBuilderTests.swift
  • Sources/RestorableAgentSession.swift
  • Sources/SessionPersistence.swift
  • cmuxTests/SessionPersistenceTests.swift

Comment thread Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeRetryPolicy.swift Outdated
@greptile-apps

greptile-apps Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a shared AgentResumeShellScriptBuilder that consolidates zsh launcher-script generation for local app restore, surface-resume bindings, and the codex-teams CLI path, while adding a bounded SQLite-lock retry policy (AgentResumeRetryPolicy) that retries only Codex startup failures whose output matches known lock signatures within a 5-second startup window.

  • New AgentResumeRetryPolicy: Defines retryable needles (database is locked, another Codex process is using its local data) with up to 3 attempts and configurable staggered backoff; codex and codexTeams launches opt in automatically.
  • New AgentResumeShellScriptBuilder: Generates FIFO-backed startup capture with dd + cat drain for bounded log capture, replaces the inline TerminalStartupReturnShellScript logic, and restores the visible shell to the session's working directory after the agent exits.
  • startupInput fallback improvement: When the retry script cannot be written (blocked temp dir), the function now falls back to the inline command rather than returning nil, covered by new regression tests.

Confidence Score: 5/5

Safe to merge — no defects found in the changed paths.

The retry policy is narrowly scoped to Codex lock signatures, the startup-window and pattern-match guards prevent runaway retries on slow or non-matching failures, and the FIFO/dd/grep pipeline for output capture correctly handles partial data and FIFO unavailability. The fallback from script to inline on blocked temp dirs is an improvement over the previous nil return. EXIT/INT/TERM traps ensure log cleanup on shutdown signals (addressing the previous review thread). The Swift layer is nonisolated value types with no actor isolation concerns, and end-to-end tests in both the package and app target cover the key retry, bound-exhaustion, and cwd-preservation scenarios.

No files require special attention.

Important Files Changed

Filename Overview
Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeShellScriptBuilder.swift Core script generation: FIFO-backed dd capture, grep pattern matching, bounded retry loop with staggered backoff, EXIT/INT/TERM trap for cleanup, and post-exit cwd restore; logic is sound and edge cases are handled.
Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeRetryPolicy.swift New public Sendable struct defining retry policy; codex and codexTeams opt in via static factory; ERE escaping and needle normalization are correct.
Sources/RestorableAgentSession.swift Wires retry policy into startupInput and resumeStartupCommand; fallback logic correctly returns inline input when script write fails.
Sources/SessionPersistence.swift Applies retry policy to SurfaceResumeBindingSnapshot; TerminalStartupReturnShellScript reduced to a thin shim; behavior preserved for non-codex agents.
CLI/cmux.swift codexTeamsStartupScript switched to .zsh and wired through AgentResumeShellScriptBuilder with retry policy.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App as cmux App / CLI
    participant Builder as AgentResumeShellScriptBuilder
    participant Script as Generated zsh launcher
    participant FIFO as FIFO + dd capture
    participant Codex as Codex process
    participant Shell as Login shell

    App->>Builder: commandThenReturnLines(command, workingDirectory, retryPolicy)
    Builder-->>App: zsh script lines
    App->>Script: Write and exec launcher

    loop Retry loop up to 3 times
        Script->>FIFO: mkfifo + dd capture 4096B in background
        Script->>Codex: script -F FIFO then shell -lic command
        Codex-->>Script: exit 1 with lock error on stderr
        FIFO-->>Script: captured log written
        Script->>Script: grep log for lock signature
        alt lock pattern matched AND elapsed less than 5s AND retry less than limit
            Script->>Script: cleanup log then sleep backoff then increment retry
        else no match OR slow failure OR limit reached
            Script->>Script: break
        end
    end

    Script->>Script: cleanup log then clear traps
    Script->>Shell: cd workingDirectory then exec login shell
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant App as cmux App / CLI
    participant Builder as AgentResumeShellScriptBuilder
    participant Script as Generated zsh launcher
    participant FIFO as FIFO + dd capture
    participant Codex as Codex process
    participant Shell as Login shell

    App->>Builder: commandThenReturnLines(command, workingDirectory, retryPolicy)
    Builder-->>App: zsh script lines
    App->>Script: Write and exec launcher

    loop Retry loop up to 3 times
        Script->>FIFO: mkfifo + dd capture 4096B in background
        Script->>Codex: script -F FIFO then shell -lic command
        Codex-->>Script: exit 1 with lock error on stderr
        FIFO-->>Script: captured log written
        Script->>Script: grep log for lock signature
        alt lock pattern matched AND elapsed less than 5s AND retry less than limit
            Script->>Script: cleanup log then sleep backoff then increment retry
        else no match OR slow failure OR limit reached
            Script->>Script: break
        end
    end

    Script->>Script: cleanup log then clear traps
    Script->>Shell: cd workingDirectory then exec login shell
Loading

Reviews (11): Last reviewed commit: "merge: resolve conflicts with main" | Re-trigger Greptile

@austinywang

Copy link
Copy Markdown
Contributor Author

Final feedback note: Greptile's latest summary says safe to merge and flags only the retry sleep as a discussion point. That is intentional here: the sleep is shell-side, bounded by AgentResumeRetryPolicy, scoped only to the Codex lock signature, staggered, and test-overridable to zero because there is no OS-level notification for another process releasing Codex's SQLite lock. No code change needed beyond the already-pushed bounded retry/fallback coverage.

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In
`@Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeShellScriptBuilder.swift`:
- Around line 113-119: When mkfifo fails in the conditional block starting at
line 115, the _cmux_resume_script_output variable remains set to /dev/null
instead of being updated to capture output for retry matching. This causes the
subsequent /usr/bin/script execution and the grep operation at line 160 to fail
silently since the log file remains empty. Add a fallback mechanism after the fi
statement on line 119 to set _cmux_resume_script_output to the actual
_cmux_resume_log file path when the FIFO setup fails, ensuring that output is
still captured and retry matching logic can function properly even when mkfifo
is unavailable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 95d44856-c516-4848-805e-59dae46a0eaf

📥 Commits

Reviewing files that changed from the base of the PR and between 8f319a0 and 22da1d5.

📒 Files selected for processing (2)
  • Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentResumeShellScriptBuilder.swift
  • Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/AgentResumeShellScriptBuilderTests.swift

@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Greptile encountered an error while reviewing this PR. Please reach out to support@greptile.com for assistance.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 0c94af5f Deployed Jun 18, 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.

Codex resume: transient state_5.sqlite lock fails the launch and drops the terminal to ~ (no retry, working dir lost)

3 participants