Skip to content

Fix Vault resume for non-ASCII paths - #4683

Merged
austinywang merged 4 commits into
mainfrom
issue-4587-vault-resume-cjk
May 24, 2026
Merged

austinywang merged 4 commits into
mainfrom
issue-4587-vault-resume-cjk

Conversation

@austinywang

@austinywang austinywang commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • keep Vault/restorable-agent resume startup input ASCII-only when command tokens contain non-ASCII bytes
  • share the terminal startup shell quoting helper across SessionEntry and restorable agent command builders
  • add regression coverage for Claude Vault resume and restorable agent resume with CJK working directories

Fixes #4587

Reproduction

  • Reproduced the failure mode with a CJK directory by generating the current raw cd command, mojibaking its UTF-8 bytes before zsh execution, and observing cd: no such file or directory.
  • Verified the fixed shell form reconstructs the same CJK path from ASCII octal bytes via printf.

Test plan

  • Not run locally per task instruction: local tests are prohibited.
  • Not run locally per task instruction: local reload/xcodebuild are prohibited; HQ should run CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag issue-4587-vault-resume-cjk --launch.

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


Note

Low Risk
Low risk: a small Swift concurrency annotation change that shouldn’t affect quoting behavior, but could subtly impact isolation expectations if misused.

Overview
Updates TerminalStartupShellQuoting in RestorableAgentSession.swift to be nonisolated, allowing its static shell-quoting utilities to be called from actor-isolated/concurrent code paths (e.g., session resume command building) without Swift concurrency isolation friction.

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


Summary by cubic

Fixes Vault/restorable-agent resume failures when the working directory contains non-ASCII characters by emitting ASCII-only startup input and reconstructing UTF-8 paths in the shell. Fixes #4587.

  • Bug Fixes
    • Introduced TerminalStartupShellQuoting to output ASCII-only tokens (uses "$(printf '\NNN...')" for non-ASCII, allows safe bare ASCII, otherwise single-quotes).
    • Applied the helper to SessionEntry and restorable agent command builders so cd works with CJK paths.
    • Added regression tests for restorable agent and Claude resume to assert ASCII-only input and that zsh resolves the correct directory.

Written for commit 6b29125. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced shell command generation to safely handle non-ASCII characters in file paths (paths with Chinese, accented, or other Unicode characters now work correctly in session restoration)
  • Tests

    • Added comprehensive tests to validate proper handling of non-ASCII file paths during session restoration

Review Change Stack

@vercel

vercel Bot commented May 24, 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 May 24, 2026 8:55am
cmux-staging Building Building Preview, Comment May 24, 2026 8:55am

@coderabbitai

coderabbitai Bot commented May 24, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR introduces TerminalStartupShellQuoting to safely quote shell command strings for session resumption when working directories contain non-ASCII characters. The new helper detects non-ASCII UTF-8 bytes and uses printf-based octal substitution instead of plain single-quote escaping. Existing quoting methods in RestorableAgentSession and SessionIndexModels are updated to delegate to this centralized logic, and tests verify the generated commands are ASCII-only and execute correctly in zsh.

Changes

Non-ASCII Path Shell Quoting

Layer / File(s) Summary
TerminalStartupShellQuoting implementation
Sources/RestorableAgentSession.swift
Introduces TerminalStartupShellQuoting enum with singleQuoted(_:) (detects non-ASCII and emits octal printf substitution or single-quote escaping), shellToken(_allowingBareASCII:) (bare ASCII pass-through when allowed), and asciiPrintfCommandSubstitution(for:) (builds printf octal payload).
Session resumption integration
Sources/RestorableAgentSession.swift, Sources/SessionIndexModels.swift
RestorableAgentSession.shellSingleQuoted(_:) and SessionEntry.shellSingleQuote(_:)/shellQuote(_:) are updated to delegate to TerminalStartupShellQuoting methods, removing prior inline regex and escape logic.
Non-ASCII path test coverage
cmuxTests/SessionPersistenceTests.swift
Added testRestorableAgentResumeStartupInputEscapesNonAsciiWorkingDirectoryAsAsciiShellInput and testSessionEntryClaudeResumeCommandEscapesNonAsciiCwdAsAsciiShellInput, verifying generated resume commands are ASCII-only and execute correctly with Unicode working directories via zsh subprocess validation.

Possibly related PRs

  • manaflow-ai/cmux#4398: The refactor to route SessionEntry's shell quoting through TerminalStartupShellQuoting directly affects the Grok resume command construction logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through paths so strange,
With Chinese characters—no need to change!
Now printf octal makes them pure,
In ASCII form, forever sure. 🐚✨


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error New enum TerminalStartupShellQuoting is a pure helper lacking explicit nonisolated annotation per swift-actor-isolation.md guidelines for "pure helpers, data formatters, or value-only utilities." Mark TerminalStartupShellQuoting as nonisolated enum TerminalStartupShellQuoting to explicitly opt out of main actor isolation.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (15 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Blocking Runtime ✅ Passed No blocking/timing primitives in production code. Changes are string utilities and refactoring. Test file uses Process.waitUntilExit which is permitted test-only scaffolding.
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift files (.swift), which are out of scope for the runtime-no-hacky-sleeps rule. Rule explicitly states: "Scope: TypeScript, JavaScript, shell, and non-Swift build/runtime scripts."
Cmux Swift Concurrency ✅ Passed PR introduces synchronous string-manipulation helpers and test code using Process API; no DispatchQueue, Combine, completion handlers, or fire-and-forget Tasks.
Cmux Swift @Concurrent ✅ Passed PR introduces pure synchronous string utility functions (TerminalStartupShellQuoting enum) with no async/await or concurrency implications; no @concurrent annotations needed or added.
Cmux Swift File And Package Boundaries ✅ Passed Adds +28 lines to oversized RestorableAgentSession.swift as focused bug fix for non-ASCII UTF-8 paths. Well under 250-line threshold; qualifies as allowed focused bug fix with clear extraction path.
Cmux Swift Logging ✅ Passed Production code changes contain no print, debugPrint, dump, NSLog, or ad hoc file/stdout logging; test additions are exempt from restrictions.
Cmux User-Facing Error Privacy ✅ Passed PR adds shell command encoding for non-ASCII paths but no user-facing errors; test assertions are developer-only; commands passed to shell, not displayed to users.
Cmux Full Internationalization ✅ Passed PR contains no new user-facing text. Changes are technical shell-quoting logic refactoring and test additions for non-ASCII path handling; no string catalogs or localization files modified.
Cmux Swiftui State Layout ✅ Passed PR modifies shell-quoting logic. No SwiftUI state patterns detected in modified files; check not applicable.
Cmux Architecture Rethink ✅ Passed Pure utility enum with static methods for ASCII-safe shell quoting; no timing, locking, observers, or state ownership issues; meets criteria for small correctness fix with clear invariants.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR changes do not add or materially change auxiliary windows; changes are shell quoting utilities and session data models with test-only additions, all excluded from the window-shortcut requirement.
Title check ✅ Passed The title 'Fix Vault resume for non-ASCII paths' accurately and concisely describes the main change—addressing failures when working directories contain non-ASCII characters.
Description check ✅ Passed The description includes a comprehensive summary explaining what changed and why, reproduction steps, and references the related issue, though testing was explicitly not run per task instructions.
✨ 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-4587-vault-resume-cjk

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.

@greptile-apps

greptile-apps Bot commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a mojibaking bug where Vault/restorable-agent resume startup input was built with raw UTF-8 bytes, causing cd to fail when the working directory contained non-ASCII characters (e.g., CJK paths). The fix introduces TerminalStartupShellQuoting, a shared helper that emits "$(printf '\NNN...')" for non-ASCII tokens so only ASCII bytes flow into the terminal's startup input.

  • New helper TerminalStartupShellQuoting added in RestorableAgentSession.swift with singleQuoted, shellToken, and asciiPrintfCommandSubstitution methods; the fileprivate shellSingleQuoted wrapper and SessionEntry.shellSingleQuote/shellQuote now delegate to it.
  • Bug fix in both AgentResumeCommandBuilder.shellCommand (via shellSingleQuoted) and SessionEntry.resumeCommandWithCwd (via shellQuote) so non-ASCII working-directory paths are encoded as octal printf sequences rather than embedded raw bytes.
  • Regression tests added for restorable-agent resume and Claude Vault resume, asserting ASCII-only output and running the generated cd command under /bin/zsh to confirm directory resolution.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to the shell quoting helpers used for resume startup commands, has correct octal encoding, and is covered by new regression tests that run the generated cd commands under zsh.

The new TerminalStartupShellQuoting helper correctly handles every byte range: non-ASCII bytes are octal-encoded into an ASCII printf substitution, ASCII safe-chars optionally pass through bare, and everything else is single-quoted with proper embedded-single-quote escaping. The two new tests create real CJK directories, assert ASCII-only output, and verify zsh resolves the reconstructed path — giving strong evidence the fix works end-to-end. No concurrency, logging, or localization concerns are introduced.

No files require special attention.

Important Files Changed

Filename Overview
Sources/RestorableAgentSession.swift Adds TerminalStartupShellQuoting enum with ASCII-only octal printf encoding for non-ASCII tokens; existing shellSingleQuoted wrapper now delegates to it. Logic is correct — octal format \NNN is valid for all byte values, and wrapping in "$(printf '...')" keeps the result ASCII-only and word-splitting-safe.
Sources/SessionIndexModels.swift shellSingleQuote and shellQuote now delegate to TerminalStartupShellQuoting; the cd path in resumeCommandWithCwd uses the new encoding. The withShellEnvironment function separately quotes only the value portion of assignments, which is correct.
cmuxTests/SessionPersistenceTests.swift Adds two regression tests with CJK temporary directories — one for restorable-agent resume, one for Claude Vault resume. Both assert ASCII-only output and verify zsh correctly resolves the reconstructed path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Build resume shell command token] --> B{Contains non-ASCII bytes?}
    B -- Yes --> C[asciiPrintfCommandSubstitution: Encode all bytes as octal NNN]
    C --> D[Emit ASCII-only printf substitution]
    B -- No --> E{Safe bare ASCII token?}
    E -- Yes --> F[Return token unquoted]
    E -- No --> G[Wrap in single quotes, escape embedded quotes]
    D --> H[Terminal startup input is ASCII-only]
    F --> H
    G --> H
    H --> I[Shell expands printf substitution and reconstitutes original UTF-8 path]
Loading

Reviews (2): Last reviewed commit: "fix: mark terminal startup quoting nonis..." | Re-trigger Greptile

Comment thread Sources/RestorableAgentSession.swift Outdated
Comment on lines +4 to +29
enum TerminalStartupShellQuoting {
static func singleQuoted(_ value: String) -> String {
if value.utf8.contains(where: { $0 >= 0x80 }) {
return asciiPrintfCommandSubstitution(for: value)
}
return "'" + value.replacingOccurrences(of: "'", with: "'\\''") + "'"
}

static func shellToken(_ value: String, allowingBareASCII: Bool) -> String {
if value.utf8.contains(where: { $0 >= 0x80 }) {
return asciiPrintfCommandSubstitution(for: value)
}
if allowingBareASCII,
value.range(of: "[^A-Za-z0-9_./:=+-]", options: .regularExpression) == nil {
return value
}
return singleQuoted(value)
}

private static func asciiPrintfCommandSubstitution(for value: String) -> String {
let octalBytes = value.utf8
.map { String(format: #"\%03o"#, Int($0)) }
.joined()
return #""$(printf '"# + octalBytes + #"')""#
}
}

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 Shared utility added to an already-oversized file

TerminalStartupShellQuoting is now consumed by both RestorableAgentSession.swift and SessionIndexModels.swift, making it a cross-file dependency with its own clearly-scoped responsibility. RestorableAgentSession.swift is already 1183 lines — well past the 800-line threshold — and placing a shared utility here means its natural home is a filename that doesn't describe it. A dedicated TerminalStartupShellQuoting.swift (or similar) would give the type a discoverable location and keep both consuming files from depending on each other's internals.

Rule Used: Flag Swift changes that add too much unrelated res... (source)

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!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Leaving this colocated with the resume-command code for this patch. The helper is only used by the two resume startup command builders, and extracting a new Swift file would add Xcode project wiring churn without changing the bug boundary; a broader shell-quoting cleanup should be separate.

— Claude Code

This branch was successfully deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vault Claude resume fails for non-ASCII project paths

1 participant