Skip to content

Fix stale surface refs and poisoned agent identity - #352

Merged
EtanHey merged 3 commits into
mainfrom
fix/stable-agent-addressing
Aug 4, 2026
Merged

Fix stale surface refs and poisoned agent identity#352
EtanHey merged 3 commits into
mainfrom
fix/stable-agent-addressing

Conversation

@EtanHey

@EtanHey EtanHey commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • keep repository identity owned by the registry instead of reparsing editable tab titles
  • isolate malformed agent rows in list_agents and keep healthy rows available
  • capture first-seen surface UUIDs and re-resolve them before raw send, paste, key, and close mutations
  • derive the live workspace from the stable binding and fail closed when a captured UUID disappears

Regression coverage

  • a captured numeric ref follows its original UUID after renumbering
  • a captured UUID that disappears never falls back to the recycled numeric ref
  • send_to(agent_id) works with brainlayerClaude [surface:199]
  • one corrupt row no longer takes down list_agents
  • omitted and supplied workspace close paths share the same stable route

Verification

  • bun run test -- --maxWorkers=4 — 106 files, 2,382 tests passed
  • bun run build
  • bun run pre-pr — 63 tests passed
  • live MCP probe through the branch build — 3 workspaces, 29/29 surfaces UUID-backed

Scope


Note

High Risk
Changes core terminal I/O and close paths for all mutating tools; incorrect UUID binding could block legitimate operations or, if mis-implemented, still target wrong surfaces.

Overview
Terminal mutations (send_input, send_command, send_key, move_surface, rename_tab, close_surface) now go through resolveRawSurfaceMutationRoute, which captures first-seen ref→UUID mappings during list_surfaces and re-resolves the live ref before I/O. Recycled or ambiguous refs, missing UUIDs, and workspace mismatches fail closed instead of hitting a wrong pane; write locks and delivery attribution can key off stable UUIDs.

Agent identity: auto-discovery sync no longer overwrites repo from tab titles (fixes poisoned names like brainlayerClaude [surface:199]). list_agents evaluates health per row and returns skipped_agents for bad rows while keeping healthy ones; CLI output warns about skipped IDs. resolveAgentRoute tolerates corrupt resume metadata without breaking send.

close_surface: live-agent checks and post-close state updates prefer stable UUID when available; recycled refs are refused rather than closing a new occupant.

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

Note

Fix stale surface refs and poisoned agent identity by binding mutations to stable UUIDs

  • Introduces resolveRawSurfaceMutationRoute in server.ts that binds surface refs to stable UUIDs observed during list_surfaces, then asserts the binding is still valid before any mutation (send, move, rename, close, etc.).
  • Tracks ref-to-UUID mappings per observer epoch; ambiguous refs (same ref seen with multiple UUIDs) and absent UUIDs cause the mutation to be refused with an error instead of acting on a recycled surface.
  • Fixes syncAutoRecord in agent-registry.ts to stop inferring and overwriting repo from surface_title, preventing identity corruption when titles contain surface suffixes.
  • Fixes resolveAgentRoute in agent-engine.ts to swallow errors from buildResumeCommand rather than throwing, returning a route without resume_command instead.
  • list_agents in server.ts now isolates per-agent health evaluation; failed agents are excluded from the count and reported in a skipped_agents field rather than failing the entire response.
  • Risk: all mutating surface tools (send_input, send_command, send_key, move_surface, rename_tab, close_surface) now require a prior list_surfaces call to establish UUID bindings; without it, operations on numeric refs may be refused when topology is complete enough to detect ambiguity.
📊 Macroscope summarized 9ba5a36. 4 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Summary by CodeRabbit

  • New Features

    • Added safer surface and workspace tracking to prevent commands from reaching moved, recycled, or ambiguous surfaces.
    • Agent listings now show healthy agents and clearly identify agents that could not be evaluated.
    • Surface listings include stable identifiers for more reliable operations.
  • Bug Fixes

    • Improved handling of invalid legacy session metadata while preserving stable routes.
    • Prevented accidental updates to repository information during automatic agent discovery.
    • Added safeguards for delivery, renaming, movement, closing, and lifecycle actions when surface state changes.

@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_5ecaf468-6907-4b1a-944f-fd7e22ad3ce7)

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The server now captures stable surface UUIDs and observer epochs, validates topology before mutations, and uses identity-scoped delivery locks. Agent listing reports skipped invalid records. Resume routing tolerates malformed legacy metadata, and registry synchronization preserves repository ownership.

Changes

Surface identity and agent resilience

Layer / File(s) Summary
Surface identity capture and route resolution
src/server.ts, tests/server.test.ts
Surface enumeration captures UUID bindings and observer epochs. Mutation routes reject ambiguous, stale, moved, missing, or mismatched surfaces.
Guarded delivery and surface mutations
src/server.ts, tests/server-agent-tools.test.ts, tests/server.test.ts, tests/painpoint-e2e.test.ts
Input, command, key, move, rename, boot-prompt, and background delivery paths use resolved routes, stable identities, and UUID-scoped locks.
Stable close and lifecycle protection
src/server.ts, tests/server.test.ts
Close operations revalidate topology, match agents by UUID, and refuse recycled or unresolved surface references.
Agent listing and metadata resilience
src/agent-engine.ts, src/agent-registry.ts, src/format.ts, src/server.ts, tests/server-agent-tools.test.ts
Agent listing reports skipped invalid records. Resume-command failures no longer block stable routing. Registry synchronization preserves repository metadata.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Poem

A rabbit guards each surface UUID,
While stale refs lose their path.
Corrupt agents hop to “skipped,”
Healthy ones remain in the batch.
Locks follow identities through the night,
And recycled panes stay out of sight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main changes: fixing stale surface references and poisoned agent identity, which are core objectives addressed across multiple files.
✨ 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 fix/stable-agent-addressing

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.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7db012d4a8

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/server.ts Outdated
Comment on lines +5229 to +5233
if (!topology.workspaceBySurface.has(requestedSurface)) {
return {
surface: requestedSurface,
workspace: requestedWorkspace,
stableSurfaceIdentity: null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject absent ref-only surfaces before mutation

When a complete legacy/ref-only topology does not contain requestedSurface, this branch returns a permissive route instead of failing closed. If that ref is recycled after the snapshot but before assertSurfaceMutationAllowed or the write, send_input, send_key, or even close_surface can mutate the new occupant because this route has no stable identity or effective current-binding assertion. Reject the absent ref just as the UUID path rejects an absent UUID.

AGENTS.md reference: AGENTS.md:L20-L25

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 5432932. Complete ref-only topology now rejects an absent requested ref, and regression coverage proves no write occurs.

Comment thread src/server.ts Outdated
surface.id ? [[surface.ref, surface.id] as const] : [],
),
);
captureSurfaceIdentities(stableIdByRef);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Verify the observer epoch before capturing list identities

In list_surfaces, the pane and surface snapshots are collected across multiple awaits, but their observer epoch is never captured and revalidated before this call. If the cmux backend/socket is replaced after enumeration and before capture, captureSurfaceIdentities associates the old backend's UUIDs with the new epoch; subsequent raw mutations then resolve against those stale UUIDs and remain refused until another reconnect or server restart. Capture the epoch before enumeration and discard the identities if it changes.

AGENTS.md reference: AGENTS.md:L38-L39

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 5432932. list_surfaces captures the observer epoch before enumeration and discards the identity snapshot if the epoch changes before capture; the regression then proves a later raw send binds only to the current observer.

@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_73d4beac-fa62-4a89-907e-3c5fca449eff)

@EtanHey

EtanHey commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

Review round 1 addressed at exact head 54329321b55233bc86722fdcdd9c848a0899b72a.

  • Recycled numeric refs are now marked ambiguous and refused; a vacated old ref still follows its captured UUID.
  • Stable UUIDs are included in condensed list_surfaces output.
  • Complete ref-only topology rejects absent refs.
  • Observer changes during list_surfaces discard the stale capture.
  • move_surface, rename_tab, and rename_to_task now use the same stable binding; manifest updates select by UUID.
  • list_agents renders skipped corrupt rows in text as well as structured data.
  • close_surface force:true remains deliberately fail-closed on identity/topology errors: force bypasses only the live-agent lifecycle guard, matching the defect brief’s no-numeric-fallback rule.

Verification: focused/widened 396/396; full 2,387/2,387 (twice, including pre-push); build; pre-PR 63/63; live branch MCP probe 3 workspaces, 27/27 surfaces UUID-backed.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54329321b5

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/server.ts Outdated
Comment on lines +5235 to +5236
const workspace =
topology.workspaceBySurface.get(currentRef) ?? requestedWorkspace;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor the caller's explicit workspace

When a caller supplies workspace, this resolver still enumerates every workspace and replaces the requested value with the workspace where the UUID currently appears. If the surface moved, or the caller accidentally combines a surface/UUID from workspace B with workspace A, send_input, send_command, send_key, rename_tab, or close_surface now silently mutates workspace B instead of failing as the previous workspace-scoped client call would. Constrain topology collection to requestedWorkspace or reject a binding found outside it.

AGENTS.md reference: AGENTS.md:L23-L25

Useful? React with 👍 / 👎.

Comment thread src/server.ts Outdated
Comment on lines +3243 to +3246
recordSurfaceWriteSuccess(
record.surface,
record.stableSurfaceIdentity,
context.surfaceObserverId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Capture the observer before background delivery

For a ref-only background delivery, finishDelivery reads context.surfaceObserverId only after the asynchronous write has completed. If the backend/socket owner changes after the write attempt but before completion is recorded, the old backend's success or broken-pipe failure is attributed to the new observer's same mutable ref, which can clear or poison its liveness state and affect crash detection. Store the observer identity when the delivery starts, as withSurfaceWrite already does for foreground writes.

AGENTS.md reference: AGENTS.md:L38-L39

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_d15bf8e1-7b0d-4302-a11f-5096992d3b67)

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99ab695c5d

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/server.ts
workspace,
stableSurfaceIdentity: null,
assertCurrent: async () => {
const current = await collectSurfaceTopology();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bind ref-only routes to the observer epoch

When a complete topology exposes no UUIDs, assertCurrent accepts any later complete ref-only snapshot containing the same ref, without checking that the observer epoch still matches the epoch captured during route resolution. If the socket/backend is replaced between resolution and a foreground or background mutation and the new backend also has requestedSurface, the write is authorized against that new occupant. Capture the resolution epoch in this route and reject when it changes before mutation.

AGENTS.md reference: AGENTS.md:L38-L39

Useful? React with 👍 / 👎.

Comment thread src/server.ts
Comment on lines +7595 to +7596
const screen = await client.readScreen(route.surface, {
workspace: route.workspace,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Revalidate before consolidating close safety state

With force:false and a live backing agent, the stable route is checked before this read but not immediately before it or before stateMgr.transition below. If the UUID moves and its old ref is recycled during that window, this reads the replacement pane; a done signal there can mark the original agent done, after which the final pre-close binding check merely refuses the close while leaving the registry corrupted. Revalidate the route around the safety read and before applying consolidation.

AGENTS.md reference: AGENTS.md:L11-L16

Useful? React with 👍 / 👎.

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

Caution

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

⚠️ Outside diff range comments (1)
src/server.ts (1)

5019-5031: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align delivery tracking keys with the UUID lock key.

acquireSurfaceWrite(record.lockKey, record.delivery_id) stores/write-locks by uuid:<id> for stable surfaces, but latestDeliveryBySurface and activeDeliveryBySurface still use record.surface.

For stable surface deliveries, getSurfaceWriteConflict(lockKey) can return the generic surface uuid:<id> is busy message instead of delivery <id> is still in progress, and the entry is not cleaned from activeDeliveryBySurface when finishDelivery runs.

Use one key namespace for the write lock and delivery tracking maps, or index activeDeliveryBySurface by record.lockKey as well.

🤖 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 `@src/server.ts` around lines 5019 - 5031, The startBackgroundDelivery tracking
maps use record.surface while acquireSurfaceWrite uses record.lockKey, causing
stable-surface lookups and cleanup to diverge. Update latestDeliveryBySurface,
activeDeliveryBySurface, and the corresponding finishDelivery/prune logic to
consistently use record.lockKey (or otherwise maintain equivalent lock-key
indexing), preserving delivery-specific conflict messages and cleanup for stable
UUID surfaces.
🤖 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 `@src/format.ts`:
- Around line 195-201: Normalize each skipped agent ID in the skippedAgents
warning within the formatting flow before joining them: remove control
characters and apply the same truncation policy used for healthy agent IDs,
reusing the existing normalization helper or logic. Keep the warning count and
rendering behavior unchanged while ensuring malformed IDs cannot corrupt the
response.

In `@src/server.ts`:
- Around line 10445-10454: In the catch block of evaluateServerAgentHealth, stop
returning the raw error message in skipped.error; log the full error server-side
using the existing server logging mechanism, then return a bounded, client-safe
reason instead. Preserve the agent_id and ensure non-Error values are normalized
for diagnostic logging without exposing filesystem paths or persisted record
contents in the response.
- Around line 5222-5223: Reduce repeated topology scans in the submit-verify
polling path around collectSurfaceTopology, resolveRawSurfaceMutationRoute,
assertCurrent, and beforeMutation by introducing a short-lived topology cache or
equivalent reuse mechanism. When an explicit workspace is available, limit
unresolved topology scans to that workspace, while preserving mutation
validation and the existing poll timeout behavior.
- Around line 7585-7590: Extract the duplicated route identity comparison into
one local backsResolvedRoute predicate after route resolution, using the
existing stableSurfaceIdentity/surface_uuid and surface_id fallback logic.
Replace both record-matching predicates, including the sites for
remainingLiveAgent and the pre-consolidation check, with calls to this predicate
and remove the redundant outer parentheses while preserving the
TERMINAL_AGENT_STATES filter.

In `@tests/server-agent-tools.test.ts`:
- Around line 6812-6852: Update the recycled-reference error assertions in
tests/server-agent-tools.test.ts (lines 6812-6852) and tests/server.test.ts
(lines 10603-10605) to match the reachable multiple-UUID refusal message,
including “Mutable surface ref”, the affected ref, and “observed for multiple
stable UUIDs; refusing”. Replace the existing “stable surface UUID|stale” and
“stable surface UUID” patterns; no production-code change is needed.

---

Outside diff comments:
In `@src/server.ts`:
- Around line 5019-5031: The startBackgroundDelivery tracking maps use
record.surface while acquireSurfaceWrite uses record.lockKey, causing
stable-surface lookups and cleanup to diverge. Update latestDeliveryBySurface,
activeDeliveryBySurface, and the corresponding finishDelivery/prune logic to
consistently use record.lockKey (or otherwise maintain equivalent lock-key
indexing), preserving delivery-specific conflict messages and cleanup for stable
UUID surfaces.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4f143d62-e97a-4924-a6ed-944f1750341b

📥 Commits

Reviewing files that changed from the base of the PR and between 8a2ae0f and 99ab695.

📒 Files selected for processing (7)
  • src/agent-engine.ts
  • src/agent-registry.ts
  • src/format.ts
  • src/server.ts
  • tests/painpoint-e2e.test.ts
  • tests/server-agent-tools.test.ts
  • tests/server.test.ts
💤 Files with no reviewable changes (1)
  • src/agent-registry.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
tests/**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.test.ts: Mirror source layout in tests (src/foo.ts -> tests/foo.test.ts).
Do not add integration tests that require a running cmux instance; tests should be fully mocked.

Files:

  • tests/painpoint-e2e.test.ts
  • tests/server.test.ts
  • tests/server-agent-tools.test.ts
src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.ts: Build the project with TypeScript (tsc) and keep source code compatible with Node 20+ and Zod-based typing.
Use the ok(data) and err(error) helpers for consistent MCP tool responses.
All MCP tool handlers must return { content: TextContent[], structuredContent?, isError? }.

Files:

  • src/agent-engine.ts
  • src/format.ts
  • src/server.ts
src/agent-engine.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Implement agent lifecycle behavior in agent-engine.ts, including spawning, monitoring, and quality tracking.

Files:

  • src/agent-engine.ts
tests/server.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Server tests should mock the cmux client via createServer({ exec, skipAgentLifecycle }).

Files:

  • tests/server.test.ts
src/server.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Register all MCP tools in server.ts, including the 33 tool handlers, and conditionally skip agent-lifecycle tools when skipAgentLifecycle: true.

Files:

  • src/server.ts
🧠 Learnings (1)
📚 Learning: 2026-03-15T10:42:35.917Z
Learnt from: EtanHey
Repo: EtanHey/cmuxlayer PR: 1
File: tests/quality-tracking.test.ts:171-200
Timestamp: 2026-03-15T10:42:35.917Z
Learning: In tests/quality-tracking.test.ts for the cmuxlayer project, ensure that at or above 80% context quality degradation, behavior depends on depth: depth-0 agents receive a /compact command; depth > 0 agents are killed and logged (kill + log). Respawn of non-root agents is out of scope for v1. Treat the design doc quality tracking section as the authoritative source for this behavior, and align test expectations accordingly.

Applied to files:

  • tests/painpoint-e2e.test.ts
  • tests/server.test.ts
  • tests/server-agent-tools.test.ts
🔇 Additional comments (28)
src/format.ts (1)

162-184: LGTM!

src/agent-engine.ts (1)

5045-5058: 🗄️ Data Integrity & Integration

Keep resumable consistent with the omitted resume_command.

When buildResumeCommand throws, this route reports resumable: true but excludes resume_command, so consumers should handle resume identity separately from an executable resume command.

src/server.ts (14)

5164-5174: The linear scan allocates an entries array per call, but the surrounding collectSurfaceTopology() enumeration dominates the cost. No action needed.


531-536: LGTM!

Also applies to: 916-918


2220-2224: LGTM!

Also applies to: 2347-2349, 2396-2398


3245-3256: LGTM!

Also applies to: 3267-3267


3566-3589: LGTM!


3809-3809: LGTM!

Also applies to: 3893-3894, 4531-4531, 4652-4652


5131-5162: LGTM!


5251-5346: LGTM!


5730-5730: LGTM!

Also applies to: 5792-5804, 5837-5839


6722-6746: LGTM!

Also applies to: 6833-6881, 6901-6926, 7033-7096, 7170-7193, 7387-7422


7537-7562: LGTM!

Also applies to: 7633-7645, 7657-7678, 7691-7703, 7718-7722, 7751-7751, 7760-7792, 7813-7813, 7822-7822


8507-8512: LGTM!

Also applies to: 8878-8878


5225-5249: 🗄️ Data Integrity & Integration

Surface ref collisions across workspaces are already rejected by collectSurfaceTopology.

collectSurfaceTopology builds workspaceBySurface without filtering workspaces and passes the pairs through validateSurfaceIdentityBijection, which marks shared surface refs in one snapshot as non-bijective. The snapshot then becomes incomplete and resolveRawSurfaceMutationRoute refuses the ref-only path, so duplicate refs inside one topology snapshot cannot cause a wrong UUID/workspace binding.

			> Likely an incorrect or invalid review comment.

10463-10477: 🎯 Functional Correctness

No changes needed.

formatListAgents declares the skipped-Agent third argument with the matching default shape, this call site matches that signature, and count is only used to display the number of rendered healthy/filtered agent rows.

tests/server.test.ts (4)

2602-2627: The exact toHaveBeenCalledTimes(5) assertion is brittle against future topology caching, but it usefully pins the per-send transport call budget. No change needed.

Also applies to: 2751-2774


1390-1390: LGTM!

Also applies to: 1403-1403


3978-4001: LGTM!

Also applies to: 4461-4484, 4612-4635


10464-10474: LGTM!

Also applies to: 10514-10514, 10563-10573, 10634-10639

tests/server-agent-tools.test.ts (7)

768-780: LGTM!

Also applies to: 832-832, 849-853, 866-871, 881-881


4313-4365: LGTM!

Also applies to: 4372-4383


4385-4436: LGTM!


6490-6621: LGTM!


6623-6763: LGTM!


6765-6810: LGTM!

Also applies to: 6854-6932


4366-4370: 🎯 Functional Correctness

No duplicated properties are present here.

The type annotation declares agents once and skipped_agents once, so there is no TS2300 duplicate identifier error to address.

			> Likely an incorrect or invalid review comment.
tests/painpoint-e2e.test.ts (1)

777-803: LGTM!

Comment thread src/format.ts
Comment on lines +195 to +201
if (skippedAgents.length > 0) {
const skippedIds = skippedAgents.map((agent) => agent.agent_id).join(", ");
lines.push(
`\u2502 \u26a0 skipped ${skippedAgents.length} invalid agent row${skippedAgents.length === 1 ? "" : "s"}: ${skippedIds}`,
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Normalize skipped agent IDs before rendering the warning.

This code inserts agent_id values directly into the text response. A malformed row with a newline, control character, or very long ID can corrupt the response. Apply the same truncation policy used for healthy IDs and remove control characters before joining the values.

Proposed fix
-    const skippedIds = skippedAgents.map((agent) => agent.agent_id).join(", ");
+    const skippedIds = skippedAgents
+      .map(({ agent_id }) =>
+        truncate(agent_id.replace(/[\u0000-\u001f\u007f]/g, " "), 18),
+      )
+      .join(", ");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (skippedAgents.length > 0) {
const skippedIds = skippedAgents.map((agent) => agent.agent_id).join(", ");
lines.push(
`\u2502 \u26a0 skipped ${skippedAgents.length} invalid agent row${skippedAgents.length === 1 ? "" : "s"}: ${skippedIds}`,
);
}
if (skippedAgents.length > 0) {
const skippedIds = skippedAgents
.map(({ agent_id }) =>
truncate(agent_id.replace(/[\u0000-\u001f\u007f]/g, " "), 18),
)
.join(", ");
lines.push(
`\u2502 \u26a0 skipped ${skippedAgents.length} invalid agent row${skippedAgents.length === 1 ? "" : "s"}: ${skippedIds}`,
);
}
🤖 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 `@src/format.ts` around lines 195 - 201, Normalize each skipped agent ID in the
skippedAgents warning within the formatting flow before joining them: remove
control characters and apply the same truncation policy used for healthy agent
IDs, reusing the existing normalization helper or logic. Keep the warning count
and rendering behavior unchanged while ensuring malformed IDs cannot corrupt the
response.

Comment thread src/server.ts
Comment thread src/server.ts
Comment thread src/server.ts
Comment on lines +10445 to +10454
} catch (error) {
return {
agent: null,
skipped: {
agent_id: agent.agent_id,
error:
error instanceof Error ? error.message : String(error),
},
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Bound the propagated error text before returning it to the client.

Line 10451 copies the raw upstream error message into the skipped_agents response. evaluateServerAgentHealth reads record fields and filesystem paths, including cli_session_path, launch_cwd, goal_file, and inbox paths under homedir(). An error thrown from any of those paths therefore embeds absolute filesystem paths or persisted record values in the tool response.

Return a bounded reason to the caller, and log the full message server-side for diagnostics.

🛡️ Proposed change
               } catch (error) {
+                const detail =
+                  error instanceof Error ? error.message : String(error);
+                console.error(
+                  `[cmuxlayer] list_agents skipped ${agent.agent_id}:`,
+                  detail,
+                );
                 return {
                   agent: null,
                   skipped: {
                     agent_id: agent.agent_id,
-                    error:
-                      error instanceof Error ? error.message : String(error),
+                    error: detail.slice(0, 200),
                   },
                 };
               }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
return {
agent: null,
skipped: {
agent_id: agent.agent_id,
error:
error instanceof Error ? error.message : String(error),
},
};
}
} catch (error) {
const detail =
error instanceof Error ? error.message : String(error);
console.error(
`[cmuxlayer] list_agents skipped ${agent.agent_id}:`,
detail,
);
return {
agent: null,
skipped: {
agent_id: agent.agent_id,
error: detail.slice(0, 200),
},
};
}
🤖 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 `@src/server.ts` around lines 10445 - 10454, In the catch block of
evaluateServerAgentHealth, stop returning the raw error message in
skipped.error; log the full error server-side using the existing server logging
mechanism, then return a bounded, client-safe reason instead. Preserve the
agent_id and ensure non-Error values are normalized for diagnostic logging
without exposing filesystem paths or persisted record contents in the response.

Comment thread tests/server-agent-tools.test.ts
@EtanHey
EtanHey force-pushed the fix/stable-agent-addressing branch from 99ab695 to 9ba5a36 Compare August 4, 2026 13:57
@cursor

cursor Bot commented Aug 4, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_f2b408e3-d892-479f-8806-d1f5b55f4bfd)

@EtanHey
EtanHey merged commit afedd5c into main Aug 4, 2026
4 of 5 checks passed

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ba5a36ded

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/server.ts
Comment on lines 7035 to 7038
const targetRecord = resolveLatestSurfaceAgentRecord(
stateMgr,
args.surface,
route.surface,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Match moved targets by their stable UUID

When resolveRawSurfaceMutationRoute follows a captured UUID from an old ref to a new one, the tracked record can still retain the old surface_id, so this ref-only lookup returns no target. For send_input with press_enter:true, that disables submit verification and can report delivery as successful even when the interactive composer never submitted; it also bypasses the multi-paragraph composer guard. The identical lookup in send_command has the same verification problem, so both should resolve the record using route.stableSurfaceIdentity when available.

AGENTS.md reference: AGENTS.md:L11-L16

Useful? React with 👍 / 👎.

Comment thread src/server.ts
Comment on lines 7984 to +7987
? record.surface_uuid.toLowerCase() === requestedSurfaceKey ||
record.surface_uuid.toLowerCase() === observedSurfaceUuid ||
(observedSurfaceUuid === undefined &&
record.surface_id === args.surface)
record.surface_id === route.surface)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not fall back to refs for UUID-backed close records

When the close route has a stable UUID but findSurfaceByRef returns a surface without id—for example, a connector exposes UUIDs only through pane surface_ids—this fallback marks every record sharing the current mutable ref as user_killed, even records whose nonmatching UUID proves they belong to another agent. In a crash/resume collision this poisons the unrelated agent after the intended surface is closed; a stable route should match records only by route.stableSurfaceIdentity, reserving the ref fallback for genuinely ref-only routes.

AGENTS.md reference: AGENTS.md:L11-L16

Useful? React with 👍 / 👎.

Comment thread src/server.ts
paneSurfaces,
workerSurfaceIds,
args.surface,
route.surface,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve worker refs before choosing close policy

If a captured worker UUID has moved from its persisted surface_id to route.surface, workerSurfaceIds still contains only the old registry ref, while this call evaluates the new ref. chooseSurfaceClosePolicy therefore no longer recognizes the target as a worker and leaves its dedicated pane uncollapsed when its final tab closes. Build the worker set from the same UUID-aware topology used for the close route, as the lifecycle engine's close-policy path already does.

AGENTS.md reference: AGENTS.md:L20-L25

Useful? React with 👍 / 👎.

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.

1 participant