Repository navigation
C11-14: CMUX-37 Phase 1 follow-up — polish + hardening - #78
Merged
Merged
Conversation
- P8: single clock read in LiveWorkspaceSnapshotSource.capture so the ULID time prefix in snapshotId and the envelope's createdAt share one tick. - P9: drop the displayTitle fallback claim from WorkspaceSnapshotCapture doc comment; the walker only reads panelCustomTitles[panelId] and accepts nil. - P10: trim terminalType in AgentRestartRegistry.init(rows:) for symmetry with the trim already in resolveCommand; avoids an asymmetric-trim footgun. Tests: add ULID-prefix-decodes-to-clock-millis invariant in WorkspaceSnapshotCaptureTests; add trim-on-insert check in AgentRestartRegistryTests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AgentRestartRegistry carries closures and is not Equatable. Identity now lives on a required `name: String` minted at construction (`"phase1"` for the canonical singleton, test-chosen names for fixtures). ApplyOptions.== compares restartRegistry by name: both-nil is equal, both-non-nil-with-same- name is equal, every other pair is unequal. - Sources/AgentRestartRegistry.swift: add `name` stored property; require it as an init parameter; mint `phase1` with `name: "phase1"`. - Sources/WorkspaceApplyPlan.swift: replace the either-non-nil-is-unequal branch with a name-match branch. - c11Tests/AgentRestartRegistryTests.swift: pass `name:` to the two custom-registry constructors already in the file. - c11Tests/WorkspaceApplyPlanCodableTests.swift: add three equality invariants (same name → equal, different names → unequal, nil vs non-nil → unequal). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sources/WorkspaceMetadataKeys.swift is already pure Foundation and already declares `SurfaceMetadataKeyName.claudeSessionId`. Adding it to the c11-cli target's Sources build phase lets the claude-hook session-start path reference the enum directly instead of duplicating the literal "claude.session_id". - GhosttyTabs.xcodeproj/project.pbxproj: new PBXBuildFile entry (c11-cli build action) pointing at the existing fileRef; add to c11-cli Sources list next to the other shared files (Mailbox*.swift, SkillInstaller.swift). - CLI/c11.swift: replace the literal in the claude-hook session-start metadata-write with SurfaceMetadataKeyName.claudeSessionId; drop the "kept in lockstep by reader convention" commentary now that the link is real. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Before: `AgentRestartRegistry.named("phase99")` silently returned nil, and
the v2 snapshot.restore handler fell back to Phase 0 (no cc --resume
synthesis) with no telemetry. A typo in the restart_registry param was
undetectable.
After: the handler detects the named-registry-resolves-to-nil case, captures
a pre-apply warning, and splices it into ApplyResult.warnings before
encoding. Operators see the warning in both JSON output and the CLI's
`warnings:` block. Restore still succeeds — forward-compat with Phase 5
rows is preserved, because an older binary should still land the layout
even if a future registry name is unrecognised.
- Sources/TerminalController.swift: capture preApplyWarnings when
AgentRestartRegistry.named returns nil for a non-nil wire name; prepend
to applyResult.warnings before JSON encode.
Note: a focused unit test for this warning would land cleanly in a
tests_v2/ subprocess test (live snapshot.restore call with an unknown
restart_registry). The tests_v2 handler test scaffold is out of scope for
this commit; noting as a follow-up under F1 in the plan.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
c11 is single-user: the claude-hook writer treats three additional error shapes as "no live c11 app on this machine for me" (advisory) instead of `failed`: - "Permission denied" — EPERM/EACCES from connecting to a socket whose owner is another uid, or from unix(7) permission checks on the socket directory. - "Operation not permitted" — EPERM on stat/unlink of the socket path. - "Resource temporarily unavailable" — EAGAIN variant when an orphaned socket file exists but no listener accepts. Extract the string-shape predicate into Sources/CLIAdvisoryConnectivity.swift so c11Tests can unit-test the matcher directly. CLI/c11.swift's isAdvisoryHookConnectivityError now delegates to the shared predicate after unwrapping the CLIError message. Both the c11 app and c11-cli targets link the shared file; no behavioural change for app code that never looked at CLIError. - Sources/CLIAdvisoryConnectivity.swift: new file with the string predicate. - CLI/c11.swift: one-line delegator; new matches reach the CLI through it. - c11Tests/CLIAdvisoryConnectivityTests.swift: three new-shape advisory cases plus regression guards for the existing shapes and two negative controls. - GhosttyTabs.xcodeproj/project.pbxproj: wire the new source into c11, c11-cli, and c11Tests Sources phases; register the fileRefs. If multi-user c11 ever lands, revisit this call — wrong-owner should stop being advisory there. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
snapshot.list previously decoded the full WorkspaceApplyPlan per file just to read envelope-level metadata + surface count. That gets slower as snapshots accumulate in ~/.c11-snapshots/ and scales with embedded-plan size (layout tree, surface metadata) rather than envelope size. After: WorkspaceSnapshotStore.enumerate uses a JSONSerialization shallow decoder that reads only snapshot_id, created_at, c11_version, origin, surface_count, and probes plan.workspace.title + plan.surfaces.count without instantiating the Codable tree. An explicit surface_count field on the envelope caches the count so the fallback shallow probe only fires on legacy files written before this change. - Sources/WorkspaceSnapshot.swift: add optional `surfaceCount: Int?` (wire: `surface_count`) to WorkspaceSnapshotFile. Optional preserves backwards compat: pre-P1 files decode with nil, new files carry the count. - Sources/WorkspaceSnapshotCapture.swift: populate surfaceCount from plan.surfaces.count in LiveWorkspaceSnapshotSource.capture. - Sources/WorkspaceSnapshotStore.swift: new private SnapshotSummary type + readSummary(from:) that does the shallow decode. enumerate() now emits rows from the summary, falling back to plan.surfaces.count when the envelope's surface_count is absent. Tests (WorkspaceSnapshotCaptureTests): - testStoreListReadsExplicitSurfaceCountFromEnvelope — verifies the new file path (envelope count wins, even when it disagrees with the plan). - testStoreListFallsBackToPlanSurfacesCountWhenEnvelopeLacksKey — verifies the legacy path (shallow probe into plan.surfaces.count). Gates I8 (next commit): the same readSummary throw path will catch unreadable rows and emit them with a `.unreadable` marker. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Before: snapshot.list silently dropped malformed envelope files (truncated
JSON, not-a-snapshot files, etc.), leaving operators unable to see or
delete them. After: unreadable entries surface as rows with a best-effort
id (filename stem), `createdAt = .distantPast` (sorts to the bottom),
empty title, surface count 0, and a machine-readable `readability`
discriminator carrying the truncated parse error.
- Sources/WorkspaceSnapshot.swift: add `readability: Readability` field
(`.ok` / `.unreadable(String)`) to WorkspaceSnapshotIndex. Tagged
Codable: `{"status": "ok"}` or `{"status": "unreadable", "reason": ...}`.
Forward-compat: older clients that omit `readability` decode as `.ok`.
- Sources/WorkspaceSnapshotStore.swift: replace the silent `continue` with
a `catch` branch that synthesises an unreadable row. Reason truncated to
160 chars to keep the terminal table legible.
- CLI/c11.swift: runListSnapshots emits `UNREADABLE` in the title column
and `?` in the surfaces column for unreadable rows. Full reason prints
after the table under `unreadable: <id> [<path>] — <reason>`. Two new
localized strings: cli.listSnapshots.column.unreadable and
cli.listSnapshots.unreadable.prefix.
Tests (WorkspaceSnapshotCaptureTests):
- testStoreListSurfacesMalformedJSONAsUnreadableRow — verifies the
unreadable row is emitted with the right shape.
- testStoreListSortsHealthyBeforeUnreadable — sort invariant.
- testReadabilityRoundTripsThroughCodable — Codable tagged encoding.
- Retires the "silently skips malformed json" test; new behavior is
opposite by design.
The JSON `readability` field is surfaced by v2SnapshotList verbatim —
no handler change required, since WorkspaceSnapshotIndex is encoded
through its Codable conformance.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
c11 restore --in-place replaces the current workspace's content with the
restored plan instead of creating a fresh workspace. Without the flag,
running c11 restore twice produces two duplicate workspaces with no
warning.
Implementation is deliberately minimum-viable: applyToExistingWorkspace
resolves the target, closes it via TabManager.closeWorkspace, and calls
the existing apply(_:options:dependencies:) path. The target workspace's
panels and splits are closed first; the new workspace inherits the plan.
Caveat: the workspace UUID changes (a fresh workspace is minted and the
prior one is closed). Preserving the original UUID + tab position is
follow-up F4 on the plan, not in scope for C11-14.
- Sources/WorkspaceLayoutExecutor.swift: replace the Phase-0 doc comment
that promised a non-existent applyToExistingWorkspace method with a
real implementation. invalid_params failure surfaces when the target
UUID does not resolve; the v2 handler maps that through ApplyResult.
- Sources/TerminalController.swift: v2SnapshotRestore accepts optional
in_place: Bool and target_workspace_id: UUID string. Branches to the
new executor method when both are supplied; synthesises an
invalid_params failure when in_place is set without a resolvable target.
- CLI/c11.swift: --in-place / --replace flag alias. The CLI resolves the
current workspace id via v2 workspace.current and threads it as
target_workspace_id. Help text (subcommandUsage("restore")) now lists
the flag and the behavioural note. Three localized English strings
(cli.restore.usage.inPlace, cli.restore.usage.inPlace.note).
- skills/c11/references/claude-resume.md: add --in-place usage example
with the UUID-change note.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Both acceptance test methods rely on terminalPendingInput(_:), which delegates to the DEBUG-only accessor TerminalSurface.pendingInitialInputForTests. In Release builds the accessor returns nil and the assertions fail with an opaque "got '' instead" message that is not actionable. Guard each affected method with #if !DEBUG throw XCTSkip so the suite reports a clean skip in Release instead of a noisy failure. The private helper stays unchanged: returning nil in Release remains a defensive fallback that XCTSkip prevents from ever being observed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
TerminalSurface is an NSView, which AppKit convention puts on the main actor but does not enforce at the compiler level. The DEBUG-only pendingInitialInputForTests accessor reads pendingTextQueue without a lock; an off-main caller would race the queue's mutation sites. Annotate the accessor @mainactor so the compiler enforces the main-actor invariant. Existing callers (the Phase 1 acceptance harness) already run on the main actor so there's no behavioural change. Rejected the alternative fix (wrap pendingTextQueue mutations with an NSLock) because it extends lock discipline into the typing-latency hot path pendingTextBytes rebalancing. @mainactor is lighter and matches AppKit's actual threading model for NSView-derived state. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 1 acceptance puts a terminal first in the layout. Phase 3 will
lean on browser- and markdown-first plans (Blueprints author them
directly), and the walker already handles them; there was no integration
test. P7 adds one fixture per non-terminal kind and two new acceptance
methods that exercise the full capture + write + read + convert +
restore loop.
Each new test asserts:
- The distinguishing surface field round-trips byte-equal (url for
browser, filePath for markdown).
- The trailing terminal still receives the synthesised
`cc --resume <session-id>` via the Phase 1 restart registry.
Both methods route through runMixedFirstFixtureRoundTrip to keep the
test bodies symmetric, and both carry the Release XCTSkip guard from P4
because they read the DEBUG-only pendingInitialInputForTests accessor.
- c11Tests/Fixtures/workspace-snapshots/browser-first-mixed.json (new)
- c11Tests/Fixtures/workspace-snapshots/markdown-first-mixed.json (new)
- c11Tests/WorkspaceSnapshotRoundTripAcceptanceTests.swift:
testCaptureAndRestoreBrowserFirstLayout
testCaptureAndRestoreMarkdownFirstLayout
plus the shared helper. Uses the existing loadFixturePlan /
resolveWorkspace / stringMetadataValue helpers; no new test
infrastructure.
Fixtures are loaded via #filePath + relative path (same pattern as the
existing mixed-claude-mailbox fixture) so no pbxproj edit is required.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The earlier I5 fix replaced the broken `%s` printf in runListSnapshots with `String.padding(toLength:withPad:startingAt:)` but did not add a test because the c11Tests target can't easily exercise the CLI binary. This adds the missing coverage via tests_v2/ (same socket-test pattern the rest of the suite uses). The test: 1. Creates a new workspace via the v2 client. 2. Captures a snapshot via `c11 snapshot --workspace <id>` (plain output). 3. Runs `c11 list-snapshots` and asserts the header lists all six columns and the captured id + source=current appear in the row. 4. Runs `c11 list-snapshots --json` and asserts the JSON payload contains the captured id with a non-negative integer surface_count that matches an integer token in the plain-table row. 5. Cleans up the snapshot file and the workspace so repeated runs stay idempotent. Skips cleanly when ~/.c11-snapshots is not writable. CI's existing tests_v2/ workflow picks this up automatically. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Snapshot envelopes store claude.session_id values in cleartext under ~/.c11-snapshots/. The trident review asked for either (a) document the threat model, (b) redact on capture, or (c) encrypt at rest via Keychain. Operator decision captured on the C11-14 ticket is (a) document-and-ship: session ids are transcript-lookup keys, not credentials, and an attacker who can read the snapshot file already has access to ~/.claude/projects/ on the same filesystem. The new `Privacy and storage` section in skills/c11/references/claude-resume.md spells out the threat model, why Keychain encryption was rejected, and the hygiene recommendations for operators whose threat model is tighter than c11's default assumption (restrict permissions, skip shared-volume backups, delete snapshots after restore). Docs-only commit; no code change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds translations for the two new English strings introduced in the C11-14 commits (CMUX-37 Phase 1 follow-up): - cli.listSnapshots.column.unreadable - cli.listSnapshots.unreadable.prefix The other keys referenced in the plan (cli.restore.usage.inPlace, .inPlace.note, .inPlaceAndPath, socket.snapshot.restore.unknownRegistry) were not wrapped in String(localized:) by the Impl phase, so nothing to sync for them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Addresses blockers and polish from the 9-agent trident review pack at notes/trident-review-C11-14-pack-20260424-1750. PR #77 blockers were already fixed; these are follow-ups specific to this branch's diff. Blockers - F-B1 (single-workspace close no-op): `c11 restore --in-place` on a window with one workspace used to duplicate. The prior order called `TabManager.closeWorkspace` (which silently no-ops on `tabs.count <= 1`) and then `apply`, producing two workspaces. Flipped order: apply first, then close. Tab count is always >= 2 at close time, so the guard passes. Side benefit: a failed apply is now non-destructive for the common case (old workspace intact, no phantom replacement). Sources/WorkspaceLayoutExecutor.swift. - F-B2 (wrong workspace from operator focus): `--in-place` now resolves the target via the caller env (CMUX_WORKSPACE_ID / C11_WORKSPACE_ID) before falling back to `workspace.current`. Background agents on a non-focused surface previously hit `workspace.current`, which returns the operator-selected workspace, not the agent's own. Destructive commands cannot depend on operator focus per the CLAUDE.md socket focus policy. CLI/c11.swift. - F-B3 (list-snapshots --json subcommand-local flag): help has always advertised `c11 list-snapshots --json`; `runListSnapshots` was rejecting it as an unknown flag. The new tests_v2 subprocess test uses the local form and would fail on CI. `--json` is now accepted both as the global pre-subcommand flag and as a subcommand-local flag. CLI/c11.swift. Important + polish - workspace_uuid_changed warning: `applyToExistingWorkspace` now prepends a structured warning to `ApplyResult.warnings` so scripted consumers see the UUID change in the same channel as pre-apply warnings. Follow-up F4 will preserve UUIDs in place. Sources/WorkspaceLayoutExecutor.swift. - xcstrings keys for --in-place help: `cli.restore.usage.inPlace` and `cli.restore.usage.inPlace.note` were referenced from `String(localized:)` call sites but never added to `Localizable.xcstrings`. Non-English builds fell back to the English defaults. English-only entries added; translator pass picks up the other six locales. Resources/Localizable.xcstrings. - WorkspaceSnapshotStore.enumerate doc comment: said "skipped silently" even though I8 emits `unreadable` rows. Comment updated to reflect the implementation. Sources/WorkspaceSnapshotStore.swift. - Em-dashes in user-facing CLI output replaced with colons: the unreadable-row print and the unknown-restart_registry warning both use plain punctuation now. CLI/c11.swift, Sources/TerminalController.swift. Tests - c11Tests/WorkspaceLayoutExecutorAcceptanceTests.swift: +4 tests exercising `applyToExistingWorkspace`: - testInPlaceRestoreReplacesSingleWorkspaceWithoutDuplicating (F-B1 regression: count == 1 after replace, original closed) - testInPlaceRestoreReplacesTargetAndLeavesSiblingIntact (multi-workspace happy path: count stable, sibling untouched) - testInPlaceRestoreMissingTargetReturnsInvalidParams (non-destructive failure on bogus UUID) - testInPlaceRestoreValidationFailureLeavesTargetIntact (unsupported version short-circuits before any state change) Out of scope / surfaced for delegator - I2 atomic rollback on post-close apply failure: the order flip already covers the common failure mode; full atomicity (snapshot the old, swap, rollback on partial apply) is F4 / Phase 2 scope. - tests_v2 cmux-era socket paths and binary globs: pre-existing pattern across the entire tests_v2 suite. Fixing one file creates inconsistency; bulk migration is its own change. - `readSummary` vs `read(from:)` created_at "disagreement" claim (Standard Claude #4): both paths share the same two-format fallback (workspaceSnapshotDateFormatter, then withInternetDateTime). Misread. - `readSummary` "not truly header-only": JSONSerialization parses the full envelope object tree. Performance scope; deferred. - snapshot.list JSON timestamp precision (second-precision .iso8601): consistency with the store's fractional-second writes. Deferred. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three more concrete bugs called out by the review pack that I missed in the first follow-up commit. These are real round-trip / output bugs, not polish. - Empty snapshot_id for `.json` files: a file literally named `.json` produced an unreadable row with `snapshotId = ""` because `deletingPathExtension().lastPathComponent` collapses to the empty string. Fall back to the full filename (capped at 128 chars) so the row is identifiable in the plain-table column. Test added in WorkspaceSnapshotCaptureTests covers the degenerate filename. Sources/WorkspaceSnapshotStore.swift, c11Tests/WorkspaceSnapshotCaptureTests.swift. (Crit Gemini #3, Std Codex #5, Crit Codex nit on filename stem.) - `snapshot.list` JSON timestamp precision: the v2 handler encoded `WorkspaceSnapshotIndex.createdAt` with `.iso8601`, which drops subsecond precision. The store now writes fractional seconds via `workspaceSnapshotDateFormatter`, so the list response was silently truncating timestamps that the store had just written. Switched the encoder to a custom strategy that uses the shared formatter, matching the round-trip the rest of the store path already provides. Sources/TerminalController.swift. (Crit Codex nit on `v2SnapshotList`.) - Honest `readSummary` doc comment: the prior comment claimed nested `plan.layout` and `plan.surfaces[].metadata` "stay in the JSON blob untouched". `JSONSerialization.jsonObject` actually materialises the full object graph into NSDictionary/NSArray; the win is skipping the `WorkspaceApplyPlan` Codable decode, not avoiding the parse. Comment reworded to describe the real cost shape so future readers don't trust the false claim. Sources/WorkspaceSnapshotStore.swift. (Crit Gemini #1, Crit Codex nit on memory shape.) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Std Claude #13: the four acceptance scenarios in WorkspaceSnapshotRoundTripAcceptanceTests carried an identical 3-line `#if !DEBUG / throw XCTSkip(...) / #endif` preamble. Extracted a private `skipIfReleaseBuild()` helper so each call site is one line and a future skip-reason change is one place, not four. No behavior change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The default parameter in LiveWorkspaceSnapshotSource.init called defaultVersionString() from a nonisolated context. Under the c11-unit scheme (which enables strict concurrency checking), this failed because the @mainactor isolation on the enclosing class made the static method implicitly @mainactor too. Adding nonisolated makes the default-parameter callsite legal without requiring the caller to be on the main actor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Tests import Debug builds set PRODUCT_NAME="c11 DEV" which makes the Swift module name c11_DEV via the default PRODUCT_MODULE_NAME derivation. All 75+ test files use @testable import c11 which fails to find the module under the c11-unit scheme (mailbox-unit CI job) because it builds in Debug mode. Fix: explicitly set PRODUCT_MODULE_NAME=c11 so the module is always named c11 regardless of whether PRODUCT_NAME is "c11 DEV" or "c11". The app product name stays "c11 DEV" for dock/menu bar disambiguation. Also update TomlSubsetParserTests.swift to use the conditional import pattern introduced by PR #79's new Blueprint test files, as belt-and- suspenders defense. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pture Doc-comment conflict above defaultVersionString(); kept main's expanded rationale for the nonisolated annotation. Both sides already agreed on the nonisolated keyword itself. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BenevolentFutures
added a commit
that referenced
this pull request
Apr 26, 2026
Resolves four conflicts after #78 (CMUX-37 Phase 1 follow-up) landed on main while this PR was open. All resolutions preserve both sides' intent. - Sources/WorkspaceSnapshotCapture.swift: keep PR's extracted WorkspacePlanCapture.capture(workspace:) call AND main's P8 single clock-read invariant (let now = clock()) so snapshotId ULID prefix and createdAt cannot diverge by a tick. - Sources/AgentRestartRegistry.swift: keep main's required name parameter (.init(name: "phase1", rows: [...])) and merge both doc comments — main's rationale for the trailing newline contract plus the PR's note on codex --last best-effort and opencode/kimi launch-fresh semantics. - c11Tests/AgentRestartRegistryTests.swift: keep both PR's three Phase 5 row tests (codex/opencode/kimi) and main's symmetric-trim invariant test. - GhosttyTabs.xcodeproj/project.pbxproj: union both sides' new test file refs (PR's three Blueprint/RoundTrip tests + main's CLIAdvisoryConnectivity test) in both PBXGroup and PBXSourcesBuildPhase.
This was referenced Apr 26, 2026
Merged
8 tasks
BenevolentFutures
added a commit
that referenced
this pull request
May 5, 2026
Lattice ticket task_01KPMTEY4WGECM9MNZ4XARN7Y6 transitions to done after PR #118 ("CMUX-37 final push: close five 2026-05-03 smoke-test gaps") shipped on 2026-05-04. The orchestrator never recorded the merge on the ticket; this catches the bookkeeping up. Archive notes/archive/cmux-37/ preserves five generations of trident review packs (2026-04-24 through 2026-05-04) that drove the ticket and were unique to the cmux-37/* worktrees. Also archives the orphan C11-14 "CMUX-37 Phase 1 follow-up" task record from the cmux-37-phase1 worktree — its work shipped via PR #78 but the Lattice file never reconciled with main, and the C11-14 short_id has since been reassigned to a different ticket. Re-importing it would collide; preserved here as historical record instead. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
restore --in-placehardened: apply-then-close ordering (no single-workspace duplicate, non-destructive on partial apply failure) and caller-env workspace targeting (CMUX_WORKSPACE_ID/C11_WORKSPACE_IDbeforeworkspace.current), with an explicitworkspace_uuid_changedwarning to scripted callers.notes/trident-review-C11-14-pack-20260424-1750/. Consensus blockers, importants, and concrete-bug nits applied in commits63d4496a,a5e75423,037edb11. Synthesis was skipped at operator direction; fixes were applied directly from cross-reviewer consensus.Highlights
--in-placeno-op (4 reviewers), wrong-workspace targeting from operator focus (3 reviewers),list-snapshots --jsonsubcommand-local flag (CI-blocking parse mismatch).applyToExistingWorkspace(single-workspace regression, multi-workspace happy path, missing target, validation failure), missingcli.restore.usage.inPlace[.note]xcstrings keys,workspace_uuid_changedwarning surfaced onApplyResult.warnings.snapshot_idfor a literal.jsonfilename (3 reviewers),v2SnapshotListdropping fractional seconds (round-trip mismatch with the store's writes), honestreadSummarydoc comment (was overstating what JSONSerialization does), em-dashes → colons in CLI output, staleenumeratedoc,skipIfReleaseBuild()helper for the 4-way#if !DEBUG / XCTSkipduplication.readSummaryvsread(from:)created_atshape mismatch claim — both paths share the same two-format fallback.tests_v2/cmux-era hardcoding — pre-existing pattern across the entire suite.AgentRestartRegistryduplicate detection (defensive coding for a footgun no operator can hit),CLIAdvisoryConnectivityEAGAIN telemetry breadcrumb, P2 unknown-registry warning unit-test seam.Scope deviations carried over from the implementation plan
applyToExistingWorkspacemints a new workspace UUID; preserving the original ULID + tab position requiresTabManager.replaceWorkspaceContent-style surgery and is logged as plan F4. The newworkspace_uuid_changedwarning surfaces this clearly to scripted callers.snapshot.restorehandler test for the unknown-registry warning needs a tests_v2 socket flow (follow-up scope; the warning logic itself is exercised by the parameter-parsing path and tested indirectly through the round-trip acceptance suite).Test plan
c11-unitpasses, including 4 newapplyToExistingWorkspacetests and 1 new.json-stem fallback test.tests_v2/test_cli_list_snapshots_plain_table.pypasses against the new local---jsonparsing.tests_v2regression suite stays green.c11 restore --in-place <id>from a single-workspace window leaves exactly one workspace.c11 restore --in-place <id>from a background agent surface targets the agent's own workspace, not whatever workspace the operator currently has selected.c11 list-snapshots --json(subcommand-local form, advertised in help) returns valid JSON.🤖 Generated with Claude Code