Skip to content

Add Vault Pi agent restore support - #3582

Merged
austinywang merged 28 commits into
mainfrom
issue-3575-vault-pi-agent-support
May 7, 2026
Merged

austinywang merged 28 commits into
mainfrom
issue-3575-vault-pi-agent-support

Conversation

@austinywang

@austinywang austinywang commented May 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add data-driven Vault agent registrations in cmux.json, with Pi registered by default
  • detect running registered agents from cmux-scoped processes and persist targeted restore snapshots
  • list JSONL-backed registered sessions in Vault and resume Pi with pi --session <path-or-id>

Fixes #3575.

Verification

  • swiftc -frontend -parse on touched Swift files
  • python3 -m json.tool on Resources/Localizable.xcstrings and web/data/cmux.schema.json
  • git diff --check

Local tests were not run per repository task instruction; CI should validate the regression test/fix pair.


Note

Medium Risk
Adds new Vault agent registration, process-detection, and resume-command generation paths that affect session discovery and restore behavior; failures could prevent resuming sessions or mis-detect running agents.

Overview
Adds config-driven Vault agent registrations via new vault.agents in cmux.json (schema + docs/vault.md), with Pi registered by default and localized.

Vault can now detect running registered agents from cmux-scoped processes (reading argv/env via sysctl) and persist snapshots with the resolved registration so restores can generate targeted resume commands.

Extends the session index to list and resume registered/JSONL-backed agent sessions, including optional icons and safer UI behavior (only show copy/resume when a command is available), plus small concurrency cleanup around autosave and drag registry.

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


Summary by cubic

Adds a config-driven Vault agent registry with Pi enabled by default and targeted --session restores for JSONL-backed agents. Improves detection, indexing, and resume flows while keeping Hermes Agent built-in. Addresses #3575.

  • New Features

    • Config: vault.agents in cmux.json (schema + docs/vault.md) with placeholders {{sessionId}}, {{sessionPath}}, {{executable}}, {{cwd}}, {{sessionDir}}; Pi registered by default.
    • Detection/restore/indexing: scan cmux-scoped processes, extract session ids (Pi JSONL, --session-dir, project folders), build resume commands from templates, and list JSONL sessions with names/icons; copy/resume only when a valid command exists.
  • Refactors

    • Models/perf: generalized registered agents (SessionAgent.registered, RestorableAgentKind.custom); snapshots carry resolved registrations; templates are non-recursive; process scans run async off autosave and reuse per-CWD registries; autosave guard preserved across async handoff.
    • Compatibility/CI/maintenance: Hermes remains built-in; legacy direct Pi kinds decode; shared Zig download retry script; moved TerminalSurface debug accessors to support file and compacted autosave task handoff to satisfy the Swift file-length guard.

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

Summary by CodeRabbit

  • New Features

    • Vault-based custom agent registration, discovery, process-detection and resume flows surfaced in the UI (registered agents, icons, copy resume command).
    • Pi agent: detect/restore sessions and support templated resume commands.
    • Improved session discovery by inspecting running processes and captured command-line/environment.
  • Localization

    • Added Pi localization entries (en, ja).
  • Documentation

    • Vault agent configuration guide with examples and placeholders.
  • Tests

    • Added tests for Pi vault persistence and custom-agent resume behavior.
  • Bug Fixes

    • Hardened resume command handling to avoid nil-related crashes.

Add a persistence-level regression that models a Pi pane snapshot carrying a concrete JSONL session path through Vault autosave and reload. The current code cannot decode the pi agent kind, so this commit is expected to fail before the implementation commit.

Constraint: Regression test commit must precede the fix so CI can show the behavior gap.

Confidence: high

Scope-risk: narrow

Directive: Keep Pi restore targeted on --session; do not regress to --continue for pane-specific restoration.

Tested: Not run locally per repository policy.

Not-tested: Local XCTest execution intentionally skipped.
Pi exposes persistent JSONL sessions and a targeted `--session` resume path,
so Vault now treats non-built-in agents as registered restore providers instead
of adding another closed enum-only branch. The default registry includes Pi, and
cmux.json can add or override registrations with detection rules, session id
sources, cwd policy, and a resume-command template.

Constraint: Pi should resume with `pi --session <path-or-id>`, not `pi --continue`
Constraint: Local tests are not run for this repo task; validation is limited to static parsing and JSON checks before CI
Rejected: Add Pi as a hardcoded sixth Vault-only agent | it would repeat the same work for the next JSONL-backed coding agent
Rejected: Preserve Pi's original launch argv on resume | stale `--continue` or previous `--session` flags can reopen the wrong session
Confidence: medium
Scope-risk: moderate
Directive: Keep new Vault agents data-driven through `vault.agents`; do not add provider-specific restore branches unless the CLI cannot be expressed by a template
Tested: swiftc -frontend -parse on touched Swift files; python3 -m json.tool on Resources/Localizable.xcstrings and web/data/cmux.schema.json; git diff --check
Not-tested: XCTest and app build, per task instruction to use CI and final reload.sh only
@vercel

vercel Bot commented May 5, 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 7, 2026 9:23am
cmux-staging Building Building Preview, Comment May 7, 2026 9:23am

@coderabbitai

coderabbitai Bot commented May 5, 2026 •

Copy link
Copy Markdown

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

Adds a Vault agent registration subsystem: schema and cmux config support, a registry and process scanner for registered agents (including Pi), session indexing/loading for registered entries, resume-command templating and custom resume flow, UI and localization changes, tests, docs, and Xcode project wiring.

Changes

Vault Agent Registration System

Layer / File(s) Summary
Schema / Config
web/data/cmux.schema.json, Sources/CmuxConfig.swift
Adds root vault property and agents array to JSON schema; extends CmuxConfigFile to decode optional vault config.
Registry & Types
Sources/VaultAgentRegistry.swift, Sources/RestorableAgentTypes.swift
Introduces CmuxVault* types (registration, detect rule, sessionIdSource, CWD policy, registry loader). RestorableAgentKind gains .custom(String) and is refactored to Codable/Hashable with custom encoding/decoding.
Process Argument Parsing
Sources/CmuxTopProcessArguments.swift, Sources/CmuxTopSnapshot.swift
Parses kernel proc args into CmuxTopProcessArguments and adds cmuxScopedProcesses() to enumerate scoped processes used for detection.
Process Detection & Session Resolution
Sources/VaultAgentProcessScanner.swift, Sources/RestorableAgentSession.swift
Adds process scanner that matches running processes against registry detect rules, resolves session IDs (argvOption or Pi session files), produces process-detected snapshots; extends resume construction to support custom registrations and template expansion (including split/placeholder expansion).
Indexing, Loading & Wiring
Sources/SessionIndexRegisteredAgents.swift, Sources/SessionIndexStore.swift, Sources/RestorableAgentSession.swift, Sources/SessionIndexModels.swift
Adds loadRegisteredAgentEntries(...) to scan JSONL metadata, threads CmuxVaultAgentRegistry through search/load flows, merges process-detected snapshots into RestorableAgentSessionIndex, updates agent ordering to include registered agents, and integrates registered agents into SessionEntry/AgentSpecifics and resume plumbing.
Model & Presentation
Sources/SessionIndexModels.swift, Sources/SessionAgentPresentation.swift, Sources/SessionIndexView.swift
Adds RegisteredSessionAgent, SessionAgent.registered(...) and updates presentation (assetName -> optional), introduces AgentIconImage and wires UI/transcript parsing to treat .registered like other agents.
Persistence & Tests
cmuxTests/PiVaultAgentPersistenceTests.swift, cmuxTests/SessionPersistenceTests.swift
Adds Pi-focused persistence tests and adjusts tests to include RestorableAgentKind.custom mapping and related expectations.
Docs & Localization
docs/vault.md, Resources/Localizable.xcstrings
Adds documentation for Vault registrations with examples (Pi, generic agent) and localization entry sessionIndex.agent.pi (en/ja).
Project Wiring
GhosttyTabs.xcodeproj/project.pbxproj
Adds new source and test files to the Xcode project build phases and file references.
Small Safety Fixes
Sources/ContentView.swift, Sources/Workspace.swift, Sources/AppDelegate.swift
Guarded resume-command usage and converted autosave tick to async; AppDelegate now awaits process-detection-aware RestorableAgentSessionIndex loading.
Utilities & Helpers
Sources/CmuxTopProcessArguments.swift, Sources/SessionIndexRegisteredAgents.swift
Helpers for reading file heads/tails, ripgrep-backed filtering, JSONL streaming helpers, and Pi session locator utilities for session file discovery.

Sequence Diagram

sequenceDiagram
    participant User
    participant VaultPanel as Vault Panel
    participant Registry as Vault Registry
    participant ProcScanner as Process Scanner
    participant SessionStore as Session Store
    participant CmdBuilder as Resume Command Builder

    User->>VaultPanel: Open Vault / view sessions
    VaultPanel->>Registry: Load registrations (built-in + local)
    Registry-->>VaultPanel: Registrations list

    VaultPanel->>ProcScanner: Enumerate running processes
    ProcScanner->>ProcScanner: Parse argv & env (kern proc args)

    loop for each process
        ProcScanner->>Registry: Ask matches for process
        alt matches registration
            Registry-->>ProcScanner: Matching registration
            ProcScanner->>Registry: Resolve sessionId (argv/pi file)
            Registry-->>ProcScanner: sessionId
            ProcScanner-->>VaultPanel: Emit detected snapshot
        end
    end

    VaultPanel->>SessionStore: Merge detected snapshots with persisted sessions

    alt user resumes session
        User->>SessionStore: Request resume
        SessionStore->>CmdBuilder: Build resume command
        CmdBuilder->>Registry: Load registration template
        CmdBuilder->>CmdBuilder: Expand placeholders ({{sessionId}}, {{cwd}}, ...)
        CmdBuilder-->>SessionStore: Resume command
        SessionStore-->>VaultPanel: Execute resume
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through configs and parsed each proc's song,

Found Pi and friends where session files belong.
Templates stitched, placeholders snug and bright—
Vault wakes panes, restores each working light.
A rabbit claps: resume, and take flight!


Caution

Pre-merge checks failed

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

  • Ignore

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

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error DirectorySnapshot marked Sendable contains non-Sendable SessionEntry, PullRequestLink, and AgentSpecifics. ClaudeMetadataCache uses @unchecked Sendable without documented safety reason. Add Sendable to SessionEntry, PullRequestLink, AgentSpecifics or remove from DirectorySnapshot. Document @unchecked Sendable safety in ClaudeMetadataCache.
Cmux Swift Concurrency ❌ Error PR adds fire-and-forget Tasks with meaningful lifecycle in AppDelegate and SessionIndexStore without proper storage or cancellation support. Store Task handles for cancellation or redesign to propagate async/await through caller chain.
Cmux Swift Logging ❌ Error PR adds unguarded NSLog in production code (VaultAgentRegistry.swift line 356) that violates swift-logging.md rules. Uses NSLog instead of Apple's unified logging for config parsing errors. Replace NSLog with Logger per swift-logging.md. Location: Sources/VaultAgentRegistry.swift line 356 in decodeConfig() method for config parse error logging.
Cmux Architecture Rethink ❌ Error SessionDragRegistry introduces Task.sleep(for: .seconds(60)) for auto-expiration. This is a timing repair path violating architectural guidelines for lifecycle races. Replace 60-second Task.sleep cleanup with deterministic lifecycle-tied cleanup (e.g., drag end callback). Tie drag state ownership explicitly to a single MainActor coordinator rather than singleton cache with delayed auto-expiration.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift @Concurrent ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Add Vault Pi agent restore support' accurately describes the main feature: vault-driven Pi agent session restoration with data-driven registrations and process detection.
Linked Issues check ✅ Passed All objectives from #3575 are met: Pi is registered by default via vault.agents in cmux.json [#3575]; process detection with argv/env scanning captures session metadata [#3575]; resume templates with placeholders enable pi --session restore [#3575]; JSONL-backed sessions are surfaced in UI [#3575]; custom agent registration mechanism is exposed and documented [#3575].
Out of Scope Changes check ✅ Passed All changes are directly related to vault Pi agent restore and custom registration support: config parsing, process detection, session indexing, resume command generation, UI updates, and documentation. No unrelated changes detected.
Cmux Swift Blocking Runtime ✅ Passed No new blocking primitives. Process scanning uses Task.detached on background queues. Autosave converted to async. Pre-existing NSLock/Task.sleep unchanged.
Cmux Swift File And Package Boundaries ✅ Passed Files respect boundaries: SessionIndexModels (429L) coherent models, VaultAgentRegistry (360L) config, others smaller. No mixed responsibilities, appropriate scoping.
Cmux Swiftui State Layout ✅ Passed PR adds AgentIconImage view and guard logic without violating SwiftUI state patterns. No new ObservableObject/@Published/@StateObject added; list rows use value snapshots; no render-time mutations.
Description check ✅ Passed PR description covers core changes (Vault agent registration, process detection, JSONL indexing) with testing approach and verification steps, but lacks structured sections per template.
✨ 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-3575-vault-pi-agent-support

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.

Comment thread Sources/SessionIndexStore.swift Outdated
Comment thread Sources/SessionIndexStore.swift Outdated
@greptile-apps

greptile-apps Bot commented May 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds config-driven Vault agent registration (new vault.agents in cmux.json) with Pi registered by default, detects running registered agents via sysctl argv capture and JSONL session scanning, and surfaces them in the session index UI with resume support via pi --session <path-or-id>.

  • Core infrastructure: New VaultAgentRegistry.swift, VaultAgentProcessScanner.swift, and CmuxTopProcessArguments.swift implement the detection and registry subsystem with process detection correctly off-main via Task.detached.
  • Session index: SessionIndexModels.swift introduces SessionAgent.registered and SessionEntry.resumeCommand; SessionIndexRegisteredAgents.swift loads and searches JSONL-backed sessions.
  • Resume flow hardening: resumeShellCommand now accepts registrationOverride, eliminating the previously-flagged lazy registry disk read; ContentView/Workspace guard against nil resume commands.

Confidence Score: 4/5

Safe to merge with one area worth a follow-up look: non-autosave saveSessionSnapshot paths in AppDelegate may still call CmuxVaultAgentRegistry.load() synchronously on the main actor.

The autosave tick, resume command construction, and process detection are now correctly off-main. The remaining concern is that saveSessionSnapshot at AppDelegate line 3598 still invokes RestorableAgentSessionIndex.load() without a pre-loaded registry on window-close, app-termination, and several other save paths — if that load() still calls CmuxVaultAgentRegistry.load(), config files are parsed on the main thread on every state-change save.

Sources/AppDelegate.swift (non-autosave saveSessionSnapshot fallback at line 3598) and Sources/SessionIndexModels.swift (over 400 lines with mixed responsibilities).

Important Files Changed

Filename Overview
Sources/VaultAgentRegistry.swift New 365-line file with clean CmuxVaultAgentRegistry types; uses Logger.fault with localizedDescription; config parsing is off-main via Task.detached.
Sources/VaultAgentProcessScanner.swift New file adding processDetectedSnapshots; per-CWD registry merging cached via registriesByWorkingDirectory; called exclusively from loadIncludingProcessDetectedSnapshots inside Task.detached.
Sources/SessionIndexModels.swift New 440-line file mixing value models, resume-command builders, shell quoting, Claude XML parsing, and CWD display helpers — exceeds the 400-line single-responsibility threshold.
Sources/SessionIndexRegisteredAgents.swift New 226-line extension with JSONL enumeration and metadata extraction; piCWDInferred CWD reconstruction is lossy for hyphenated paths (already flagged); otherwise cancellation-aware.
Sources/RestorableAgentSession.swift Adds registrationOverride to resumeShellCommand; template rendering fails closed when any placeholder resolves to empty; lazy registry load removed.
Sources/AppDelegate.swift Autosave tick now uses await loadIncludingProcessDetectedSnapshots(); non-autosave saveSessionSnapshot at line 3598 still falls through to RestorableAgentSessionIndex.load() which may call CmuxVaultAgentRegistry.load() synchronously.
Sources/SessionIndexStore.swift Registry loading moved to defaultAgentOrderSync inside Task.detached; drag-registry TTL modernized from DispatchQueue.global().asyncAfter to Task.sleep on @mainactor.
Sources/CmuxTopProcessArguments.swift New 90-line KERN_PROCARGS2 parser via two-phase sysctl; correctly truncates to actual written bytes; Sendable value type.
scripts/download-with-retry.sh New CI download helper; uses sleep for retry backoff in a manual loop instead of curl --retry / --retry-delay.

Sequence Diagram

sequenceDiagram
    participant AD as AppDelegate
    participant RASI as SessionIndex
    participant Scanner as ProcessScanner
    participant Reg as VaultRegistry
    participant SIS as SessionIndexStore
    participant UI as SessionIndexView

    AD->>RASI: await loadIncludingProcessDetectedSnapshots
    Note over RASI: Task.detached off main
    RASI->>Reg: load workingDirectory
    Reg-->>RASI: CmuxVaultAgentRegistry
    RASI->>Scanner: processDetectedSnapshots
    Scanner->>Scanner: sysctl KERN_PROCARGS2 per process
    Scanner-->>RASI: keyed snapshots
    RASI-->>AD: RestorableAgentSessionIndex

    UI->>SIS: search registered agent
    Note over SIS: Task.detached utility
    SIS->>Reg: load for cwdFilter
    SIS->>SIS: loadRegisteredAgentEntries
    SIS-->>UI: SessionEntry array

    UI->>UI: resumeCommandWithCwd via registrationOverride
Loading

Reviews (19): Last reviewed commit: "Keep merged Swift files within guard bud..." | Re-trigger Greptile

Comment thread Sources/SessionIndexStore.swift Outdated
Comment thread Sources/SessionAgentPresentation.swift Outdated
Comment thread Sources/SessionIndexStore.swift Outdated
Comment thread Sources/RestorableAgentTypes.swift Outdated

@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: 13

🤖 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 `@cmuxTests/SessionPersistenceTests.swift`:
- Around line 1004-1085: The new test function
testPiVaultAgentSnapshotRoundTripBuildsTargetedSessionCommand pushes the file
past the Swift length budget; move that test into a new test file (e.g.,
SessionPersistencePiVaultTests.swift) by copying the entire test function into a
new XCTestCase subclass (ensure you include import XCTest and any required test
helpers or makeSnapshot/SessionPersistenceStore visibility), remove the original
function from cmuxTests/SessionPersistenceTests.swift, and run tests to confirm
compilation and that loadedAgent assertions (kind, sessionId, resumeCommand)
still pass.

In `@Sources/CmuxTopSnapshot.swift`:
- Around line 47-50: Move the new proc-args parser and its helper type out of
CmuxTopSnapshot.swift into its own file under Sources to satisfy the file-length
budget: create a new Swift source file (e.g., CmuxTopProcessArguments.swift)
containing the CmuxTopProcessArguments struct plus the related parser code and
any helper functions currently between lines 348–462, and update visibility
(internal) so CmuxTopProcessSnapshot and CmuxTopProcessScope can still reference
it; ensure there are no behavior changes and remove the moved code from
CmuxTopSnapshot.swift.

In `@Sources/RestorableAgentSession.swift`:
- Around line 607-630: The matches(_:) implementation in
CmuxVaultAgentDetectRule should require all configured detect clauses to be true
(conjunctive), not return early when processName matches; change matches(_:) to
compute a Boolean for processNameMatch and a Boolean for argvContainsMatch and
return their conjunction (or just argvContainsMatch if argvContains is empty).
Also change the argvContains matching logic in matches(_:) so each needle is
treated as a substring match (use argument.range(of: needle, options:
[.caseInsensitive, .literal]) != nil or search the joinedArguments for needles
with "/" or " "), instead of only doing exact/token equality—ensure you still
handle lastPathComponent by applying substring search to the lastPathComponent
rather than equality. Ensure these changes are made in the
CmuxVaultAgentDetectRule.matches(_ process: VaultObservedAgentProcess) method.

In `@Sources/RestorableAgentTypes.swift`:
- Around line 134-505: Extract the CmuxVault* declarations
(CmuxVaultConfigDefinition, CmuxVaultAgentRegistration,
CmuxVaultAgentDetectRule, CmuxVaultAgentSessionIDSource,
CmuxVaultAgentCWDPolicy, CmuxVaultAgentRegistry and any helpers like
decodeConfig/configPaths/findLocalConfig) into a new Swift source file and
remove them from Sources/RestorableAgentTypes.swift so the original file falls
under the CI length budget; ensure you add explicit access control
(public/internal/private) for the moved types and their initializers/props as
appropriate rather than relying on implicit internal visibility, keep the same
Codable/Sendable conformances and preserve method names (init(from:), isValidID,
defaultExecutable, load, registration, findLocalConfig, decodeConfig,
configPaths) so callers compile, and update any imports or module-level
references if necessary.
- Around line 183-189: The ID validation in RestorableAgentTypes (the guard
using Self.isValidID(id) in the init/from-decoder path) must also reject IDs
that collide with built-in agent kinds; update the guard to additionally ensure
RestorableAgentKind(rawValue: id) yields nil (or is not a built-in case) and
throw the same DecodingError.dataCorruptedError with the existing
debugDescription if it does collide. Apply the same check to the other
decoding/validation block referenced around lines 227-230 so both decoding paths
consistently reject built-in names.
- Around line 328-358: The decoder currently checks for a non-empty argvOption
first which lets argvOption override an explicit type; change the logic to
decode the container and the required "type" first (using
decoder.container(keyedBy: CodingKeys.self) and let type = ...), then branch on
type (.piSessionFile / .argvOption). For the .argvOption branch decode and
validate argvOption is present and non-empty (throw
DecodingError.dataCorruptedError if blank/missing), and for the .piSessionFile
branch ensure argvOption is not non-empty (if argvOption exists and
.trimmingCharacters... is not empty, throw a dataCorruptedError to reject
conflicting config). Ensure the default case still throws an unknown type error.

In `@Sources/SessionAgentPresentation.swift`:
- Around line 10-14: The current special-case in the switch branch for case
.registered(let id) returns a hardcoded localized "Pi" when id == "pi",
preventing any user-provided vault.agents[].name override; change the logic to
prefer the registry-provided name from
CmuxVaultAgentRegistry.load().registration(id: id)?.name first (for all ids or
at least for "pi") and only fall back to the localized "Pi" (or id) if no
registry name exists so that vault overrides are honored.

In `@Sources/SessionIndexStore.swift`:
- Around line 1446-1651: Move the entire registered-agent block into a new file
as an extension on SessionIndexStore (e.g.
Sources/SessionIndexStore+RegisteredAgents.swift): create `extension
SessionIndexStore { ... }` and paste the declarations
RegisteredAgentJSONLMetadata, loadRegisteredAgentEntries,
registeredSessionRoots, enumerateRegisteredJSONLCandidates,
extractRegisteredJSONLMetadata, firstString, and piCWDInferred there, preserving
the nonisolated and static behavior; change access-control so any extension
members that need to be callable from the original file are not marked private
(remove `private` so they are internal), while keeping types/vars that are only
used inside the new file private; do not alter logic, only move code and adjust
access modifiers to internal where cross-file access is required.
- Around line 1641-1651: The piCWDInferred(from:) helper builds a candidate path
by decoding a directory like "--body--" but currently returns it without
verifying it exists on disk, which can misattribute CWDs when segments contain
hyphens; modify nonisolated private static func piCWDInferred(from url: URL) to
construct the candidate ("/" + body.replacingOccurrences(of: "-", with: "/"))
and then call FileManager.default.fileExists(atPath:isDirectory:) (with a Bool
var) to ensure the candidate exists and is a directory—only return the candidate
when the check succeeds, otherwise return nil (mirroring the
decodeClaudeProjectDir pattern).
- Around line 711-716: defaultAgentOrder() currently calls
CmuxVaultAgentRegistry.load() without passing the project working directory,
which omits project-scoped agents; change defaultAgentOrder() to accept a
workingDirectory parameter (e.g. defaultAgentOrder(workingDirectory: URL?)) and
call CmuxVaultAgentRegistry.load(workingDirectory: workingDirectory) inside it,
then update all callers that have a cwd (notably loadDirectorySnapshot(cwd:),
searchSessions(.directory) branch, and any scanAll() path that can supply a cwd)
to pass their cwd through; keep the existing behavior for callers without a cwd
by passing nil so built-in-only behavior remains.
- Around line 1539-1555: registeredSessionRoots and
extractRegisteredJSONLMetadata currently special-case the "pi" id; instead add a
data-driven dispatch on CmuxVaultAgentRegistration (e.g., a new enum/property
like directoryEncoding or cwdFromPathPolicy on CmuxVaultAgentRegistration or
extend sessionIdSource) and implement a protocol/strategy (e.g.,
PiSessionLocator-conforming type) that exposes methods used now
(projectDirectoryName(for:) and piCWDInferred(from:)). Replace the id == "pi"
checks in registeredSessionRoots and extractRegisteredJSONLMetadata with calls
to registration.directoryEncoding.resolveProjectDirectory(for:cwdFilter, root:)
or registration.cwdPolicy.inferCWD(from:path) so behavior is driven by
registration metadata rather than hardcoded ids, and update registration
parsing/fixtures to set the new policy for Pi registrations.
- Around line 188-208: The fallback when RestorableAgentKind(rawValue:
registration.id) succeeds but AgentResumeCommandBuilder.resumeShellCommand(...)
returns nil must not synthesize a hardcoded "<defaultExecutable> --session
<sessionId>" but instead expand registration.resumeCommand using the same
template expansion used by customResumeArguments in RestorableAgentSession.swift
(substitute {{sessionId}}, {{sessionPath}}, {{executable}}, {{cwd}},
{{sessionDir}} etc.) so that registration.resumeCommand is honored; replace the
hardcoded return in that case with a call to the existing template expansion
helper (or extract a small helper that performs the template substitution if
needed) and return its result. Also remove inline provider-specific checks
(registration.id == "pi") from registeredSessionRoots and
extractRegisteredJSONLMetadata by converting that behavior into registration
metadata (e.g., projectDirectoryEncoding) or a Pi-specific helper and dispatch
based on metadata rather than literal id. Finally split the registered-agent
related types/functions (RegisteredAgentJSONLMetadata,
loadRegisteredAgentEntries, registeredSessionRoots,
enumerateRegisteredJSONLCandidates, extractRegisteredJSONLMetadata, firstString,
piCWDInferred) into a new file to satisfy the file-length budget and update
imports/usages accordingly.

In `@web/data/cmux.schema.json`:
- Around line 86-107: The schema for sessionIdSource should mirror the runtime
decoder CmuxVaultAgentSessionIDSource.init(from:) by requiring the "type"
property and enforcing "argvOption" when type == "argvOption"; update the
sessionIdSource object definition to include "required": ["type"] and add an
"if": { "properties": { "type": { "const": "argvOption" } } } with a
corresponding "then": { "required": ["argvOption"] } so invalid objects like {}
or { "type": "argvOption" } are rejected at validation time (keep existing
additionalProperties: false and the "type"/"argvOption" property specs).
🪄 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: 40f40bdf-e2c5-4c23-b38b-67c7e381138a

📥 Commits

Reviewing files that changed from the base of the PR and between d7dae39 and 8376b64.

📒 Files selected for processing (11)
  • Resources/Localizable.xcstrings
  • Sources/CmuxConfig.swift
  • Sources/CmuxTopSnapshot.swift
  • Sources/RestorableAgentSession.swift
  • Sources/RestorableAgentTypes.swift
  • Sources/SessionAgentPresentation.swift
  • Sources/SessionIndexStore.swift
  • Sources/SessionIndexView.swift
  • cmuxTests/SessionPersistenceTests.swift
  • docs/vault.md
  • web/data/cmux.schema.json

Comment thread cmuxTests/SessionPersistenceTests.swift Outdated
Comment thread Sources/CmuxTopSnapshot.swift Outdated
Comment thread Sources/RestorableAgentSession.swift Outdated
Comment thread Sources/RestorableAgentTypes.swift Outdated
Comment thread Sources/RestorableAgentTypes.swift Outdated
Comment thread Sources/SessionIndexStore.swift Outdated
Comment thread Sources/SessionIndexStore.swift Outdated
Comment thread Sources/SessionIndexStore.swift Outdated
Comment thread Sources/SessionIndexStore.swift Outdated
Comment thread web/data/cmux.schema.json
The extensible Vault agent support had grown several already-large files, so this splits the new registry, process scanning, model, and JSONL indexing code into focused files while preserving the same runtime paths.

Constraint: CI enforces Swift file-length budgets and the budget file should not be raised for this feature.
Rejected: Refresh the Swift file-length budget | would accept avoidable debt instead of isolating coherent code.
Confidence: high
Scope-risk: narrow
Directive: Keep future Vault agent integrations in the registry/scanner/index helper files instead of growing SessionIndexStore or RestorableAgentSession again.
Tested: swiftc -frontend -parse on changed Swift surfaces; JSON schema/localization validation; project.pbxproj lint; Swift file-length budget guard; git diff --check
Not-tested: Local XCTest execution per repository policy
Comment thread Sources/SessionIndexModels.swift
Comment thread Sources/SessionIndexRegisteredAgents.swift
Review feedback exposed a few places where the new registration path still behaved like a Pi-specific patch. This keeps detection, display names, session directory scoping, and resume generation driven by registration metadata while avoiding synchronous display-path config reads.

Constraint: Local XCTest is disabled by repository policy, so this round uses static validation and CI for behavioral execution.
Rejected: Keep processName as an early-success detector | it ignored configured argv constraints and broadened matches unexpectedly.
Rejected: Reconstruct Pi cwd from encoded path without checking the filesystem | hyphenated project names make that inference lossy.
Confidence: medium
Scope-risk: moderate
Directive: New Vault agents should add registration metadata or sessionIdSource behavior instead of literal agent-id checks in session indexing.
Tested: swiftc -frontend -parse on changed Swift surfaces; JSON schema/localization validation; Swift file-length budget guard; git diff --check
Not-tested: Local XCTest execution per repository policy
CircleCI failed without exposing logs through the public status script, and the previous review-fix commit introduced a static mutable display-name cache. This moves the cache to the existing shared-instance plus NSLock pattern used elsewhere in the app so Swift concurrency checks do not see unsafely shared mutable static state.

Constraint: CircleCI log output is not available through the PR check helper or public CircleCI output endpoint.
Rejected: Leave static mutable dictionary behind a static lock | Swift 6 still treats shared mutable static state as unsafe.
Confidence: medium
Scope-risk: narrow
Directive: Keep synchronous display-name lookup in memory-only caches; registry disk reads belong off the main actor.
Tested: swiftc -frontend -parse on affected Swift files; Swift file-length budget guard; git diff --check
Not-tested: Local XCTest execution per repository policy
The tagged CI build showed RestorableAgentSession and SessionIndexStore could not see methods moved into the new Vault extension files. The build phase had entries, but the matching PBXFileReference records were missing, so Xcode omitted those files from compilation.

Constraint: The only available failure evidence was the CI reload log; local app builds are reserved for the final mandated reload.
Rejected: Move the extension methods back into the large files | that would regress the file-length guard fix.
Confidence: high
Scope-risk: narrow
Directive: When splitting Swift files in the Xcode project, add both PBXBuildFile and PBXFileReference records before relying on project lint.
Tested: project.pbxproj lint; git diff --check
Not-tested: Local build/test per task constraints
Comment thread Sources/VaultAgentProcessScanner.swift Outdated
Process detection already receives the global Vault registry, so it can merge project-local registrations lazily per working directory instead of re-reading every config for every observed process. The scanner now keeps a per-cwd registry cache and the registry exposes a focused project-config merge path.

Constraint: Review feedback flagged synchronous registry loads inside the process scan loop.
Rejected: Reload the complete registry for each process | preserves the latency issue and repeats global config reads.
Confidence: high
Scope-risk: narrow
Directive: Keep process scanning on preloaded registries plus bounded per-cwd project overrides; do not add per-process config disk reads back to the loop.
Tested: swiftc -frontend -parse for Sources and cmuxTests Swift files; JSON lint for Localizable.xcstrings and cmux.schema.json; plutil -lint GhosttyTabs.xcodeproj/project.pbxproj; swift_file_length_budget.py; git diff --check.
Not-tested: Local XCTest and app launch before final mandated reload, per task instructions.
The per-cwd registry cache helper used the same name as the registry parameter, which made Swift resolve member accesses against the local function instead of the loaded registry. Renaming the helper keeps the cached project-config merge behavior and restores compilation.

Constraint: CI compile failed in VaultAgentProcessScanner after the review-performance fix.
Rejected: Revert the cache | would reintroduce repeated registry disk reads from the review finding.
Confidence: high
Scope-risk: narrow
Directive: Avoid helper names that shadow high-value parameters in scanner code.
Tested: swiftc -frontend -parse for Sources and cmuxTests Swift files; JSON lint for Localizable.xcstrings and cmux.schema.json; plutil -lint GhosttyTabs.xcodeproj/project.pbxproj; swift_file_length_budget.py; git diff --check.
Not-tested: Local XCTest and local app build, per task instructions.
Comment thread Sources/SessionIndexRegisteredAgents.swift
Comment thread Sources/VaultAgentProcessScanner.swift
Comment thread Sources/SessionAgentPresentation.swift Outdated
Comment thread Sources/RestorableAgentSession.swift Outdated
Review found that registered Vault agent support still had a few UI-path hazards: process detection ran synchronously from autosave, display names used a shared lock-backed cache, and registered session searches reread the registry per agent. This moves process detection behind a detached async load for autosave, carries registered display names as value data, reuses the already-loaded registry during search, and keeps JSONL parsing alive long enough to collect branch metadata.

Constraint: Local tests are not run in this repository; CI owns XCTest and UI coverage.
Rejected: Keep the display-name singleton with NSLock | leaves actor isolation unverifiable and was the current review blocker.
Rejected: Make every session snapshot save async | broader call-site migration than needed for the autosave hot path.
Confidence: high
Scope-risk: moderate
Directive: Do not put Vault process scans or registry config reads back into SwiftUI render or autosave main-actor paths.
Tested: xcrun swiftc -frontend -parse on touched Swift files; git diff --check; python3 scripts/swift_file_length_budget.py; ./scripts/reload.sh --tag issue-3575-vault-followups.
Not-tested: Local XCTest per repository policy.

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

Caution

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

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

3434-3503: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Revalidate autosave preconditions after the awaited index load.

The old flow checked the typing quiet-period and termination state immediately before saving. Now there's an await in between, so the user can type again or shutdown can start while loadIncludingProcessDetectedSnapshots() is in flight, and this method will still proceed with a scrollback-less autosave using a stale now. That can defeat the quiet-period and race the shutdown snapshot.

💡 Suggested fix
-        let now = Date()
 `#if` DEBUG
         let fingerprintStart = ProcessInfo.processInfo.systemUptime
 `#endif`
         let restorableAgentIndex = await RestorableAgentSessionIndex.loadIncludingProcessDetectedSnapshots()
+        guard Self.shouldRunSessionAutosaveTick(isTerminatingApp: isTerminatingApp) else {
+            return
+        }
+        if let remainingQuietPeriod = remainingSessionAutosaveTypingQuietPeriod() {
+#if DEBUG
+            cmuxDebugLog(
+                "session.save.skipped reason=typing_recent includeScrollback=0 source=\(source) " +
+                    "retryMs=\(Int((remainingQuietPeriod * 1000).rounded()))"
+            )
+#endif
+            scheduleDeferredSessionAutosaveRetry(after: remainingQuietPeriod)
+            return
+        }
+        let now = Date()
         let autosaveFingerprint = sessionAutosaveFingerprint(
             includeScrollback: false,
             restorableAgentIndex: restorableAgentIndex
         )
🤖 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/AppDelegate.swift` around lines 3434 - 3503, In
finishSessionAutosaveTick, after awaiting
RestorableAgentSessionIndex.loadIncludingProcessDetectedSnapshots() and before
calling saveSessionSnapshot, revalidate the autosave preconditions by updating
now = Date(), recomputing autosaveFingerprint via
sessionAutosaveFingerprint(includeScrollback: false, restorableAgentIndex:
restorableAgentIndex), and re-running
Self.shouldSkipSessionAutosaveForUnchangedFingerprint(...) (using
lastSessionAutosaveFingerprint, lastSessionAutosavePersistedAt,
isTerminatingApp, etc.); if it returns true, log the skip (cmuxDebugLog as
above) and return instead of proceeding to saveSessionSnapshot; preserve/update
debug timing variables (fingerprintMs/saveMs) accordingly.
🤖 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 `@Sources/RestorableAgentSession.swift`:
- Around line 229-237: The current template expansion uses
templateParts.compactMap which drops any part that expands to an empty string,
causing option flags to be left without values; update the logic in
RestorableAgentSession.swift (the block using templateParts, replacements and
producing resolved) to fail-fast: iterate/map over templateParts applying
replacements and trimming, and if any resulting trimmed part is empty then
return nil for the whole expansion instead of removing that part; otherwise
return the full array of resolved parts (i.e., do not use compactMap that
filters out empty elements).
- Line 212: Change the function signature that currently returns an optional
collection (-> [String]?) to a non-optional array (-> [String]) and replace any
returns of nil inside that function with an empty array ([]); update any call
sites handling an optional (notably where resumeArguments is passed into
resumeShellCommand) to treat an empty array as the same "no args" case (remove
optional unwrapping or nil-coalescing). Ensure the function declaration and all
internal return paths use [String] and that callers like
resumeArguments/resumeShellCommand rely on empty-array semantics rather than
optionality.

In `@Sources/SessionIndexModels.swift`:
- Around line 217-236: In the .registered case do not fall back to returning the
raw template string registration.resumeCommand if
AgentResumeCommandBuilder.resumeShellCommand(...) fails; instead propagate the
failure (e.g., return nil or throw so the caller surfaces the validation error).
Update the case handling around AgentResumeCommandBuilder.resumeShellCommand (in
SessionIndexModels.swift) to remove the fallback to registration.resumeCommand
and ensure the caller of this switch (or the function) properly handles the
nil/throw result so invalid registrations don't produce literal templates passed
to the shell.
- Around line 73-89: The Codable implementation for SessionAgent currently
encodes only rawValue which loses RegisteredSessionAgent.name on round-trips;
update encode(to:) and init(from:) in the SessionAgent Codable conformance so
that when the enum case is .registered(RegisteredSessionAgent), you encode an
object containing both id and name (e.g., a keyed container with "id" and
optional "name") and when decoding reconstruct .registered by decoding that
keyed representation (falling back to RegisteredSessionAgent(id: decodedId,
name: nil) if name is absent); ensure the init(from:) still supports legacy
plain-string rawValue decoding to remain backward compatible with other cases.

In `@Sources/SessionIndexRegisteredAgents.swift`:
- Around line 67-84: The code currently hardcodes candidate.url.path as the
session id when creating SessionEntry, which breaks agents that supply a native
session id via sessionIdSource (e.g. argvOption). Change the builder to derive a
sessionId from the metadata/sessionIdSource (e.g. let sessionId =
metadata.sessionId ?? candidate.url.path) and then use that sessionId in both
the SessionEntry id string ("\(registration.id):\(sessionId)") and the sessionId
field instead of candidate.url.path so registered agents resume with their
native id.

In `@Sources/VaultAgentRegistry.swift`:
- Around line 337-345: The decodeConfig(at:fileManager:) currently swallows any
JSONC preprocessing or JSON decoding errors and returns nil; change it to catch
and surface errors instead of silently returning nil by wrapping
JSONCParser.preprocess and JSONDecoder().decode calls in do/catch, and on
failure log a warning that includes the path and the caught error (use the
project's logging utility or os_log/print if none), then return nil; reference
decodeConfig, JSONCParser.preprocess, JSONDecoder().decode, and CmuxConfigFile
when locating where to add the do/catch and the warning message.
- Around line 63-71: The DecodingError thrown when validating resumeCommand is
misleading because the guard accepts either "{{sessionId}}" or "{{sessionPath}}"
but the debugDescription only mentions "{{sessionId}}"; update the
debugDescription in the DecodingError.dataCorruptedError call (forKey:
.resumeCommand, in: container) to reference both accepted placeholders (e.g.,
mention "{{sessionId}} or {{sessionPath}}") so the error accurately reflects the
allowed templates for resumeCommand.

---

Outside diff comments:
In `@Sources/AppDelegate.swift`:
- Around line 3434-3503: In finishSessionAutosaveTick, after awaiting
RestorableAgentSessionIndex.loadIncludingProcessDetectedSnapshots() and before
calling saveSessionSnapshot, revalidate the autosave preconditions by updating
now = Date(), recomputing autosaveFingerprint via
sessionAutosaveFingerprint(includeScrollback: false, restorableAgentIndex:
restorableAgentIndex), and re-running
Self.shouldSkipSessionAutosaveForUnchangedFingerprint(...) (using
lastSessionAutosaveFingerprint, lastSessionAutosavePersistedAt,
isTerminatingApp, etc.); if it returns true, log the skip (cmuxDebugLog as
above) and return instead of proceeding to saveSessionSnapshot; preserve/update
debug timing variables (fingerprintMs/saveMs) accordingly.
🪄 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: 7f7df1b3-164e-46d4-873f-0f4e27cff014

📥 Commits

Reviewing files that changed from the base of the PR and between 8376b64 and 05435db.

📒 Files selected for processing (17)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Sources/AppDelegate.swift
  • Sources/CmuxConfig.swift
  • Sources/CmuxTopProcessArguments.swift
  • Sources/CmuxTopSnapshot.swift
  • Sources/RestorableAgentSession.swift
  • Sources/RestorableAgentTypes.swift
  • Sources/SessionAgentPresentation.swift
  • Sources/SessionIndexModels.swift
  • Sources/SessionIndexRegisteredAgents.swift
  • Sources/SessionIndexStore.swift
  • Sources/VaultAgentProcessScanner.swift
  • Sources/VaultAgentRegistry.swift
  • cmuxTests/PiVaultAgentPersistenceTests.swift
  • cmuxTests/SessionPersistenceTests.swift
  • docs/vault.md
  • web/data/cmux.schema.json

Comment thread Sources/RestorableAgentSession.swift Outdated
Comment thread Sources/RestorableAgentSession.swift Outdated
Comment thread Sources/SessionIndexModels.swift Outdated
Comment thread Sources/SessionIndexModels.swift Outdated
Comment thread Sources/SessionIndexRegisteredAgents.swift
Comment thread Sources/VaultAgentRegistry.swift
Comment thread Sources/VaultAgentRegistry.swift
Comment thread Sources/SessionIndexStore.swift Outdated

@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: 4

♻️ Duplicate comments (7)
Sources/VaultAgentRegistry.swift (2)

65-72: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Decode error mentions only {{sessionId}} but the guard also accepts {{sessionPath}}.

The guard accepts either placeholder, but the debugDescription only mentions {{sessionId}}, so users get misleading feedback when their template uses {{sessionPath}}.

🛠️ Proposed fix
             throw DecodingError.dataCorruptedError(
                 forKey: .resumeCommand,
                 in: container,
-                debugDescription: "Vault agent resumeCommand must include {{sessionId}}"
+                debugDescription: "Vault agent resumeCommand must include {{sessionId}} or {{sessionPath}}"
             )
🤖 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/VaultAgentRegistry.swift` around lines 65 - 72, The DecodingError
debug message for the resumeCommand guard is misleading because the guard allows
either "{{sessionId}}" or "{{sessionPath}}" but the
DecodingError.dataCorruptedError debugDescription only mentions "{{sessionId}}";
update the error text in the throw inside the guard (the
DecodingError.dataCorruptedError call forKey: .resumeCommand, in: container) to
reference both placeholders (e.g., require "{{sessionId}}" or "{{sessionPath}}")
so users receive accurate feedback when the resumeCommand uses
"{{sessionPath}}".

337-345: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Silently swallowing config decode errors makes broken cmux.json files invisible.

Both JSONCParser.preprocess and JSONDecoder().decode failures are mapped to nil here. For a config-driven restore path, a typo in cmux.json will silently make custom Vault registrations disappear with no diagnostic. Catch the errors and surface a warning that includes the file path so misconfiguration is debuggable.

🛠️ Proposed fix
     private static func decodeConfig(at path: String, fileManager: FileManager) -> CmuxConfigFile? {
         guard fileManager.fileExists(atPath: path),
               let data = fileManager.contents(atPath: path),
-              !data.isEmpty,
-              let sanitized = try? JSONCParser.preprocess(data: data) else {
+              !data.isEmpty else {
             return nil
         }
-        return try? JSONDecoder().decode(CmuxConfigFile.self, from: sanitized)
+        do {
+            let sanitized = try JSONCParser.preprocess(data: data)
+            return try JSONDecoder().decode(CmuxConfigFile.self, from: sanitized)
+        } catch {
+            NSLog("[Vault] Failed to decode cmux config at %@: %@", path, String(describing: error))
+            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/VaultAgentRegistry.swift` around lines 337 - 345, The
decodeConfig(at:fileManager:) helper is swallowing both JSONCParser.preprocess
and JSONDecoder().decode errors and returning nil, which hides malformed
cmux.json; update decodeConfig to explicitly do-try-catch around
JSONCParser.preprocess and JSONDecoder().decode (referencing
JSONCParser.preprocess, JSONDecoder().decode and CmuxConfigFile) and when either
throws log a warning that includes the path and the caught error details so
misconfigured files are visible, then return nil on error; keep the existing
file existence/data checks and only attempt parsing inside the do-catch blocks.
Sources/SessionIndexRegisteredAgents.swift (1)

73-86: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

sessionId is hardcoded to candidate.url.path and ignores registration.sessionIdSource.

Storing the JSONL file path as the session id is only correct for piSessionFile-style sources. For a registration whose sessionIdSource == .argvOption(...), the native session id should be parsed from the JSONL contents (e.g. an id/sessionId/session_id field) and used both here and in the SessionEntry.id composite. As written, any non-Pi registered agent will resume with the JSONL path passed where its CLI expects a real session id.

🛠️ Sketch

Add a metadata field for the native session id and prefer it over the file path:

     private struct RegisteredAgentJSONLMetadata {
         var title: String = ""
         var cwd: String?
         var branch: String?
+        var sessionId: String?
     }

Then in extractRegisteredJSONLMetadata, capture firstString(in: object, keys: ["sessionId", "session_id", "id"]) and:

-            matches.append(SessionEntry(
-                id: "\(registration.id):\(candidate.url.path)",
+            let resolvedSessionId = metadata.sessionId ?? candidate.url.path
+            matches.append(SessionEntry(
+                id: "\(registration.id):\(resolvedSessionId)",
                 agent: .registered(RegisteredSessionAgent(registration: registration)),
-                sessionId: candidate.url.path,
+                sessionId: resolvedSessionId,
🤖 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/SessionIndexRegisteredAgents.swift` around lines 73 - 86, The
SessionEntry is using candidate.url.path as sessionId and in the composite id,
which ignores registration.sessionIdSource; update the code to prefer a native
session id extracted from the JSONL metadata (add a metadata field like
nativeSessionId in whatever struct extractRegisteredJSONLMetadata fills) and use
that nativeSessionId when present for both SessionEntry.sessionId and the
SessionEntry.id composite (fall back to candidate.url.path only if
nativeSessionId is nil). Modify extractRegisteredJSONLMetadata to call
firstString(in:object, keys:["sessionId","session_id","id"]) and populate the
new nativeSessionId, and ensure RegisteredSessionAgent/SessionEntry construction
uses that new field instead of always using candidate.url.path.
Sources/RestorableAgentSession.swift (2)

229-237: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

compactMap silently drops empty-expanded template parts, producing malformed argv.

For a template like pi --session {{sessionId}} --session-dir {{sessionDir}}, when sessionDir is unset (no registration.sessionDirectory), {{sessionDir}} collapses to "" and gets filtered out, leaving ["pi", "--session", "<id>", "--session-dir"] — an option flag missing its required value, then concatenated and sent to the shell.

Fail closed when any placeholder yields an empty value rather than emitting a corrupt command.

🛠️ Proposed fix
-        let resolved = templateParts.compactMap { part -> String? in
+        var resolved: [String] = []
+        for part in templateParts {
             var value = part
             for (key, replacement) in replacements {
                 value = value.replacingOccurrences(of: "{{\(key)}}", with: replacement)
             }
             let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
-            return trimmed.isEmpty ? nil : trimmed
+            // If a template part contained a placeholder that resolved to "",
+            // abort entirely rather than emitting an option flag without its value.
+            guard !trimmed.isEmpty else { return nil }
+            resolved.append(trimmed)
         }
         return resolved.isEmpty ? nil : resolved
🤖 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 229 - 237, The code in the
template expansion (using templateParts.compactMap) silently drops parts that
expand to empty strings, corrupting argv; change the logic in the expansion loop
(the block that builds resolved from templateParts and replacements) to use map
(or keep all parts) and detect any part that contained a placeholder but trimmed
to empty — if any replacement yields an empty trimmed value, return nil (fail
closed) rather than filtering it out; reference the templateParts/replacements
loop and the resolved return so the function returns nil when any placeholder
expansion is empty.

212-214: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Prefer [String] over [String]? to satisfy discouraged_optional_collection.

SwiftLint flags this signature, and an empty array and nil are semantically equivalent here (both make resumeArguments → resumeShellCommand return nil via the !argv.isEmpty guard).

🛠️ Proposed fix
-    private static func customResumeArguments(
+    private static func customResumeArguments(
         registration: CmuxVaultAgentRegistration,
         sessionId: String,
         launchCommand: AgentLaunchCommandSnapshot?
-    ) -> [String]? {
+    ) -> [String] {
         let templateParts = splitShellWords(registration.resumeCommand)
-        guard !templateParts.isEmpty else { return nil }
+        guard !templateParts.isEmpty else { return [] }
         ...
-        return resolved.isEmpty ? nil : resolved
+        return resolved
     }
🤖 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 212 - 214, Change the
function signature from returning [String]? to a non-optional [String] and
return an empty array instead of nil; specifically, in the function that calls
splitShellWords(registration.resumeCommand) replace "guard
!templateParts.isEmpty else { return nil }" with a non-optional-friendly return
(e.g., "guard !templateParts.isEmpty else { return [] }" or simply return
templateParts) and update the signature to -> [String]; ensure call sites like
resumeArguments and resumeShellCommand (which already gate on !argv.isEmpty)
continue to work with the empty-array semantics so no further behavioral changes
are needed.
Sources/SessionIndexModels.swift (2)

217-237: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't fall back to the unresolved template string.

When AgentResumeCommandBuilder.resumeShellCommand returns nil, line 236 returns registration.resumeCommand — the raw template (e.g. pi --session {{sessionId}}). That string then gets cd && ... wrapped by resumeCommandWithCwd and shipped to a real shell, turning a bad registration / missing context into a broken resume rather than surfacing the failure.

🛠️ Proposed fix
         case .registered(let registration):
-            if let command = AgentResumeCommandBuilder.resumeShellCommand(
+            return AgentResumeCommandBuilder.resumeShellCommand(
                 kind: .custom(registration.id),
                 sessionId: sessionId,
                 launchCommand: AgentLaunchCommandSnapshot(
                     launcher: registration.id,
                     executablePath: nil,
                     arguments: [registration.defaultExecutable],
                     workingDirectory: cwd,
                     environment: nil,
                     capturedAt: nil,
                     source: "vault"
                 ),
                 workingDirectory: nil,
                 registrationOverride: registration,
                 includeWorkingDirectoryPrefix: false
-            ) {
-                return command
-            }
-            return registration.resumeCommand
+            ) ?? ""
         }

(Or, better, propagate the failure by making resumeCommand optional so callers can skip emitting a broken resume entirely.)

🤖 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/SessionIndexModels.swift` around lines 217 - 237, The current code
falls back to returning the unresolved template string
registration.resumeCommand when
AgentResumeCommandBuilder.resumeShellCommand(...) returns nil, which produces a
broken resume command; change the control flow so that when resumeShellCommand
returns nil you do NOT return registration.resumeCommand but instead propagate
the failure (e.g., make the containing method return an optional and return nil,
or make registration.resumeCommand optional and return nil) so callers can skip
emitting a broken resume; update all callers to handle the optional result;
relevant symbols: AgentResumeCommandBuilder.resumeShellCommand,
registration.resumeCommand, and the switch/case that currently returns
registration.resumeCommand.

87-90: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Encoding only rawValue drops RegisteredSessionAgent.name across Codable round-trips.

encode(to:) writes just rawValue (the agent id), and init(rawValue:) always reconstructs .registered(RegisteredSessionAgent(id: value)) with name == nil. Any persistence/IPC round-trip of a non-built-in registered agent loses the configured display name and the UI falls back to the raw id.

🤖 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/SessionIndexModels.swift` around lines 87 - 90, encode(to:) currently
writes only rawValue (the agent id), which drops RegisteredSessionAgent.name
during Codable round-trips; update the Codable implementation so encode(to:)
encodes both the id and optional name (e.g., via a keyed container like keys
"id" and "name" or by encoding RegisteredSessionAgent directly) and update
init(from:) to decode those same keys and reconstruct
.registered(RegisteredSessionAgent(id: decodedId, name: decodedName)). Ensure
uses of rawValue remain for built-ins but preserve RegisteredSessionAgent.name
when present (referenced symbols: encode(to:), init(from:),
RegisteredSessionAgent, rawValue, name).
🤖 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 `@Sources/RestorableAgentSession.swift`:
- Around line 27-32: The current logic always calls
CmuxVaultAgentRegistry.load(...) when registrationOverride is nil, causing
unnecessary filesystem walks for built-in kinds; change it to only call
CmuxVaultAgentRegistry.load when a custom agent ID might be needed by first
checking kind.customAgentID (or registrationOverride) before invoking load.
Concretely, update the construction of registry/customRegistration so that you
defer or guard the call to CmuxVaultAgentRegistry.load(...) — e.g., compute
whether kind.customAgentID != nil (or registrationOverride == nil &&
kind.customAgentID != nil) and only then call CmuxVaultAgentRegistry.load(...)
to populate registry and resolve customRegistration (referencing
registrationOverride, kind.customAgentID, CmuxVaultAgentRegistry.load, and
customRegistration).

In `@Sources/SessionAgentPresentation.swift`:
- Around line 22-24: The registered case currently returns the hardcoded
"AgentIcons/OpenCode" which mislabels vault-registered agents (e.g., Pi); update
the data flow so the UI resolves an icon from the registration instead of always
using OpenCode: add an iconAssetName (or similar) property to
CmuxVaultAgentRegistration and change SessionAgentPresentation's assetName
resolution for the .registered case to read registration.iconAssetName (falling
back to a neutral generic asset like "person.crop.circle" or a shipped Pi asset
only when id == "pi" and no user override exists); ensure the registry lookup
path that yields a CmuxVaultAgentRegistration is updated to populate
iconAssetName so assetName no longer returns the hardcoded
"AgentIcons/OpenCode".

In `@Sources/SessionIndexModels.swift`:
- Around line 24-26: Replace the localized string usage for the Pi brand with a
direct literal: instead of returning String(localized: "sessionIndex.agent.pi",
defaultValue: "Pi") when id == "pi", return the brand literal "Pi" directly so
the agent displayName is not treated as translatable text; update the branch
that checks id == "pi" (the conditional containing the String(localized:) call)
to return "Pi".

In `@Sources/SessionIndexStore.swift`:
- Around line 1067-1078: When handling .agent(let a) where a is .registered, you
must fetch the vault registry and perform the search with the caller's
current/project working directory instead of passing workingDirectory: nil and
cwdFilter: nil; update the branch that creates registry via
Self.vaultAgentRegistry(workingDirectory: nil) to use the appropriate
currentDirectory (or the entry's cwd) and pass that directory through as
cwdFilter to the call to
Self.searchAgent(needle:agent:cwdFilter:offset:limit:errorBag:registry:) so
project-scoped registrations are found (mirror how the .directory branch threads
cwdFilter into defaultAgentOrder/loadRegisteredAgentEntries).

---

Duplicate comments:
In `@Sources/RestorableAgentSession.swift`:
- Around line 229-237: The code in the template expansion (using
templateParts.compactMap) silently drops parts that expand to empty strings,
corrupting argv; change the logic in the expansion loop (the block that builds
resolved from templateParts and replacements) to use map (or keep all parts) and
detect any part that contained a placeholder but trimmed to empty — if any
replacement yields an empty trimmed value, return nil (fail closed) rather than
filtering it out; reference the templateParts/replacements loop and the resolved
return so the function returns nil when any placeholder expansion is empty.
- Around line 212-214: Change the function signature from returning [String]? to
a non-optional [String] and return an empty array instead of nil; specifically,
in the function that calls splitShellWords(registration.resumeCommand) replace
"guard !templateParts.isEmpty else { return nil }" with a non-optional-friendly
return (e.g., "guard !templateParts.isEmpty else { return [] }" or simply return
templateParts) and update the signature to -> [String]; ensure call sites like
resumeArguments and resumeShellCommand (which already gate on !argv.isEmpty)
continue to work with the empty-array semantics so no further behavioral changes
are needed.

In `@Sources/SessionIndexModels.swift`:
- Around line 217-237: The current code falls back to returning the unresolved
template string registration.resumeCommand when
AgentResumeCommandBuilder.resumeShellCommand(...) returns nil, which produces a
broken resume command; change the control flow so that when resumeShellCommand
returns nil you do NOT return registration.resumeCommand but instead propagate
the failure (e.g., make the containing method return an optional and return nil,
or make registration.resumeCommand optional and return nil) so callers can skip
emitting a broken resume; update all callers to handle the optional result;
relevant symbols: AgentResumeCommandBuilder.resumeShellCommand,
registration.resumeCommand, and the switch/case that currently returns
registration.resumeCommand.
- Around line 87-90: encode(to:) currently writes only rawValue (the agent id),
which drops RegisteredSessionAgent.name during Codable round-trips; update the
Codable implementation so encode(to:) encodes both the id and optional name
(e.g., via a keyed container like keys "id" and "name" or by encoding
RegisteredSessionAgent directly) and update init(from:) to decode those same
keys and reconstruct .registered(RegisteredSessionAgent(id: decodedId, name:
decodedName)). Ensure uses of rawValue remain for built-ins but preserve
RegisteredSessionAgent.name when present (referenced symbols: encode(to:),
init(from:), RegisteredSessionAgent, rawValue, name).

In `@Sources/SessionIndexRegisteredAgents.swift`:
- Around line 73-86: The SessionEntry is using candidate.url.path as sessionId
and in the composite id, which ignores registration.sessionIdSource; update the
code to prefer a native session id extracted from the JSONL metadata (add a
metadata field like nativeSessionId in whatever struct
extractRegisteredJSONLMetadata fills) and use that nativeSessionId when present
for both SessionEntry.sessionId and the SessionEntry.id composite (fall back to
candidate.url.path only if nativeSessionId is nil). Modify
extractRegisteredJSONLMetadata to call firstString(in:object,
keys:["sessionId","session_id","id"]) and populate the new nativeSessionId, and
ensure RegisteredSessionAgent/SessionEntry construction uses that new field
instead of always using candidate.url.path.

In `@Sources/VaultAgentRegistry.swift`:
- Around line 65-72: The DecodingError debug message for the resumeCommand guard
is misleading because the guard allows either "{{sessionId}}" or
"{{sessionPath}}" but the DecodingError.dataCorruptedError debugDescription only
mentions "{{sessionId}}"; update the error text in the throw inside the guard
(the DecodingError.dataCorruptedError call forKey: .resumeCommand, in:
container) to reference both placeholders (e.g., require "{{sessionId}}" or
"{{sessionPath}}") so users receive accurate feedback when the resumeCommand
uses "{{sessionPath}}".
- Around line 337-345: The decodeConfig(at:fileManager:) helper is swallowing
both JSONCParser.preprocess and JSONDecoder().decode errors and returning nil,
which hides malformed cmux.json; update decodeConfig to explicitly do-try-catch
around JSONCParser.preprocess and JSONDecoder().decode (referencing
JSONCParser.preprocess, JSONDecoder().decode and CmuxConfigFile) and when either
throws log a warning that includes the path and the caught error details so
misconfigured files are visible, then return nil on error; keep the existing
file existence/data checks and only attempt parsing inside the do-catch blocks.
🪄 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: 1638625a-a642-4864-8569-6dd0ef14eb2d

📥 Commits

Reviewing files that changed from the base of the PR and between 05435db and 670d0a5.

📒 Files selected for processing (8)
  • Sources/AppDelegate.swift
  • Sources/RestorableAgentSession.swift
  • Sources/SessionAgentPresentation.swift
  • Sources/SessionIndexModels.swift
  • Sources/SessionIndexRegisteredAgents.swift
  • Sources/SessionIndexStore.swift
  • Sources/VaultAgentRegistry.swift
  • cmuxTests/PiVaultAgentPersistenceTests.swift

Comment thread Sources/RestorableAgentSession.swift Outdated
Comment thread Sources/SessionAgentPresentation.swift
Comment thread Sources/SessionIndexModels.swift
Comment thread Sources/SessionIndexStore.swift
CircleCI macOS unit tests failed before reaching XCTest because the Zig tarball download was reset by the remote peer. The shared install-zig command now uses curl retries for both the archive and signature downloads while preserving minisign verification.

Constraint: CircleCI failed in dependency bootstrap with curl exit 56 before code or tests ran

Rejected: Empty retry commit | would retrigger CI without improving the flaky bootstrap path

Confidence: high

Scope-risk: narrow

Directive: Keep signature verification after download retries; retries should not bypass minisign

Tested: git diff --check

Tested: ruby YAML.load_file('.circleci/config.yml')

Not-tested: local Xcode build/tests per repository policy and user xcodebuild restriction
Both required CI surfaces failed before build/test execution because the Zig tarball download was reset by the remote peer. Direct Zig downloads across GitHub workflows now retry transient curl failures and resume partial archives; CircleCI uses the same resume behavior inside its shared install-zig command.

Constraint: Local xcodebuild is prohibited and the failures occurred in remote dependency bootstrap

Rejected: Patch activation only | would leave the same reset-prone Zig download in other CI entrypoints

Confidence: high

Scope-risk: narrow

Directive: Keep retry/resume flags on every direct ziglang.org tarball download unless replacing them with a shared installer

Tested: git diff --check

Tested: ruby YAML.load_file for changed workflow configs

Not-tested: local Xcode build/tests per repository policy and user xcodebuild restriction
The retry/resume path kept activation alive but the transfer stayed stuck inside Install zig for an extended period. Direct Zig downloads now treat sustained sub-1KB/s transfers as transient failures, allowing curl to retry and resume instead of holding macOS runners indefinitely.

Constraint: Activation CI remained in Install zig after the reset-prone download path was hardened

Rejected: Wait for runner timeout | provides no new code evidence and delays the same stalled transfer

Confidence: high

Scope-risk: narrow

Directive: Keep speed-limit, retry, and continue-at flags together for direct Zig tarball downloads

Tested: git diff --check

Tested: ruby YAML.load_file for changed workflow configs

Not-tested: local Xcode build/tests per repository policy and user xcodebuild restriction
Comment thread Sources/RestorableAgentSession.swift
Custom Vault resume templates now replace placeholders by scanning the original token once instead of mutating through dictionary iteration. Placeholder-looking text inside session ids, cwd values, or other replacement data remains literal and cannot be expanded by a later replacement pass.

Constraint: Cursor review found dictionary iteration could make replacement expansion order-dependent

Rejected: Ordered dictionary iteration only | deterministic but still allows replacement values to be recursively expanded

Confidence: high

Scope-risk: narrow

Directive: Do not reintroduce sequential replacingOccurrences over a mutable template token for registered resume templates

Tested: git diff --check

Tested: added XCTest coverage for literal placeholder text inside sessionId replacement values

Not-tested: local XCTest/build per repository policy and user xcodebuild restriction
The direct curl flags still allowed macOS CI to lose a nearly-complete Zig archive when ziglang.org reset the connection. A shared downloader now bounds each attempt, resumes partial output with --continue-at, and retries explicitly so GitHub Actions and CircleCI use the same recovery behavior.

Constraint: Activation was cancelled in Install zig after 30 minutes and CircleCI release reset at roughly 95% of the Zig archive

Rejected: Rely on curl --retry alone | observed CircleCI did not recover the reset transfer

Confidence: high

Scope-risk: moderate

Directive: Keep direct Zig archive downloads routed through scripts/download-with-retry.sh unless replacing the installer with a cached artifact

Tested: bash -n scripts/download-with-retry.sh

Tested: git diff --check

Tested: ruby YAML.load_file for changed workflow configs

Not-tested: local Xcode build/tests per repository policy and user xcodebuild restriction
The PR branch was merged with origin/main before the next update. YAML workflow conflicts were resolved by taking origin/main exactly as requested; the remaining Swift test conflict keeps main's Pi environment case and the branch's empty environment grouping for cursor, rovodev, factory, and custom agents.

Constraint: User required merging origin/main before PR updates

Constraint: YAML conflicts must take origin/main and no YAML files may be manually edited

Confidence: high

Scope-risk: moderate

Tested: git diff --name-only --diff-filter=U returned no paths

Tested: conflicted YAML files match origin/main

Tested: git diff --cached --check

Not-tested: local XCTest/build per repository policy and user xcodebuild restriction

@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 1 potential issue.

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 204025a. Configure here.

Comment thread scripts/download-with-retry.sh
Comment thread Sources/VaultAgentRegistry.swift Outdated
Comment thread Sources/RestorableAgentSession.swift Outdated
origin/main added Pi as a native restorable kind while this branch intentionally keeps Pi registry-owned for Vault discovery and project overrides. The merged enum therefore needed to keep direct .pi values encodable without adding Pi to the hook-kind allCases list.

Constraint: Direct xcodebuild is forbidden; CI activation uses the tagged reload script.

Constraint: YAML files must not be edited in this iteration.

Rejected: Add Pi back to RestorableAgentKind.allCases | that would bypass the registry-owned Pi hook path and block project overrides.

Confidence: high

Scope-risk: narrow

Directive: Keep Pi out of RestorableAgentKind.allCases while Vault owns the default Pi registration.

Tested: git diff --check

Not-tested: Local build/tests, per repo policy and no direct xcodebuild constraint.
Resume command construction should not discover Vault config by walking the filesystem on demand. Restorable snapshots now carry the registration resolved at registry load or process-detection time, so custom resume templates remain available without main-thread disk reads.

Constraint: Review feedback flagged synchronous registry loading during restore and production NSLog usage.

Constraint: YAML files are off-limits for this iteration.

Rejected: Keep lazy CmuxVaultAgentRegistry.load in resumeShellCommand | preserves legacy fallback but keeps disk IO in the restore hot path.

Confidence: high

Scope-risk: moderate

Directive: Custom restorable agents must receive a registration before resume command construction; do not reintroduce fallback disk reads in AgentResumeCommandBuilder.

Tested: git diff --check

Not-tested: Local build/tests, per repo policy and no direct xcodebuild constraint.
origin/main adds Hermes Agent as a built-in provider while this branch keeps Pi and custom agents registry-owned through Vault. The merge keeps Hermes in the built-in session/search/resume switches, keeps dynamic Vault agents on the registered path, and restores explicit pi decoding so legacy direct Pi values do not collapse into custom registrations.

Constraint: PR branch must be merged with current origin/main before updating the PR.

Constraint: YAML workflow files were not edited during this merge.

Rejected: Treat Hermes as a Vault registration | Hermes landed on main as a built-in provider with dedicated index and hook support.

Confidence: medium

Scope-risk: moderate

Directive: Keep registry-owned agents out of built-in allCases when project config must be able to override them.

Tested: git diff --cached --check

Not-tested: Local tests per repository policy; CI will validate after push.
Comment thread Sources/AppDelegate.swift Outdated
The autosave tick sets its in-flight guard before crossing into an async MainActor task. Keeping the AppDelegate alive through that handoff makes the task always reach finishSessionAutosaveTick, where the existing defer clears the guard in debug and release builds.

Constraint: Greptile flagged the weak-self early return as a path that could strand sessionAutosaveTickInFlight.

Rejected: Add a guard-else reset | there is no instance to mutate when weak self is nil; removing the nil path preserves the invariant directly.

Confidence: high

Scope-risk: narrow

Tested: git diff --check

Not-tested: Local tests per repository policy; CI will validate after push.
origin/main now adds settings, event, process-detail, and test-target changes. The only manual conflict resolution was in the Xcode project test target membership, where both PiVaultAgentPersistenceTests and AgentSessionAutoResumeSettingsTests need to stay in the cmuxTests group and sources phase.

Constraint: Merge origin/main into issue-3575-vault-pi-agent-support before continuing PR work.

Constraint: No .yml/.yaml conflicts were edited.

Rejected: Taking either side of project.pbxproj test membership | would drop a valid test file from one branch.

Confidence: high

Scope-risk: moderate

Tested: git diff --cached --check

Not-tested: Local tests per repository policy; final verification uses the required reload script.
The merge with origin/main pushed GhosttyTerminalView and AppDelegate over the CI file-length guard. Move TerminalSurface debug metadata accessors into the existing small support file and compact the autosave task handoff so the guard passes without editing workflow YAML or increasing the budget.

Constraint: CI failed workflow-guard-tests on scripts/swift_file_length_budget.py.

Constraint: Do not edit .yml/.yaml files or xcodebuild directly.

Rejected: Refreshing .github/swift-file-length-budget.tsv | accepts debt instead of reducing the over-budget files.

Confidence: high

Scope-risk: narrow

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: git diff --check

Not-tested: Local app/test build per repository policy; final verification uses CI and required reload script.

This branch was successfully deployed

1 active deployment
Preview – cmux — afe4fea8 Deployed May 7, 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.

Add Pi Coding Agent support to Vault session restore (or custom Vault agent registration)

2 participants