Skip to content

Agent pane: an answer to one ask never revokes another ask's gesture ticket - #32113

Merged
teamleaderleo merged 2 commits into
feat-cmux-nextfrom
lane/permission-answer-ticket-per-ask
Nov 2, 2026
Merged

teamleaderleo merged 2 commits into
feat-cmux-nextfrom
lane/permission-answer-ticket-per-ask

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

What broke

On 2026-10-21, while a folder trust question was open, Leo answered an agent's question card (AskUserQuestion) and got "Couldn't send your answer. Try again. approve this on the Mac app: allowing a permission is accepted only from the cmux app, never from the CLI, a script or an agent". He asked: "WTH COULD IT POSSIBLY RESUME WTH KIND OF RESTRICTION IS THAT IT F***ING SHULDNT BE WTFFFF".

The main causes are already fixed on base: #25424 and #25440 bind a question's answers into the relayed proof, and #25022 fixed a lost person-key enrollment. One gap is left in the host. AgentPaneGestureIntent.slot kept one _acpmux/permission_respond ticket per connection. When an answer to a second ask was reserved before the first answer's frame went out, it revoked the first ask's ticket. The first answer then went to another machine's acpmux unproved, and acpmux refused it with that same message.

The fix

  • slot keys _acpmux/permission_respond by its permissionId (_acpmux/permission_respond#<permissionId>), the way fix(next): preserve gesture proof for rapid composer sends #29745 keys prompts by promptId. A newer answer to the same ask still replaces the older one.
  • Each connection keeps at most 8 answer tickets (permissionTicketsKept), dropping the oldest, like prompt tickets.
  • Redeem is unchanged: a ticket is still single-use and must match its exact pick (session, permission, option, answers).

Tests

AgentPaneRelayedAllowTests.answersToTwoAsksReservedBackToBackKeepTheirOwnTickets. The red commit 67774b5 adds the two-asks case and the same-ask replacement; the fix adds the cap check. The PR's CI compile and test jobs are the check: this lane's host can't run a compile check.

Risk

Low, next to the trust boundary. A ticket still needs a real click and still covers only its own exact answer. The only change is that answers to different asks no longer revoke each other, and their number stays bounded.

Rule this serves (Leo, P0, 2026-10-21): a pending folder trust question must never block steering, sending, or answering a question. Follows #32074.

🤖 Generated with Claude Code

Agent session: agent session


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes an Agent pane bug where answering one ask revoked another ask's permission answer ticket, so the first answer went out unproved and acpmux refused it.

  • Keys _acpmux/permission_respond tickets by permissionId so each ask keeps its own ticket; a newer answer to the same ask still replaces the older one.
  • Caps answer tickets at 8 per connection, dropping the oldest.

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

View guided diff Turn on auto-fix

teamleaderleo and others added 2 commits November 2, 2026 10:02
…re tickets

Red on base: the second answer's reserve revokes the first ask's ticket,
since _acpmux/permission_respond has one slot per connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-786825ad708e439b96f4
AgentPaneGestureIntent.slot keyed _acpmux/permission_respond only by
method, so a second answer reserved before the first answer's frame went
out revoked the first ticket. For a relayed chat the first answer then
went out unproved and acpmux refused it ("allowing a permission is
accepted only from the cmux app"). Key the slot by permissionId, as
#29745 keyed prompts by promptId, and keep at most 8 answer tickets per
connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-786825ad708e439b96f4
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review fc9cb7f: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr32113 -f ref=lane/permission-answer-ticket-per-ask -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base a615dd9; does not block

Shipped in:

@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at fc9cb7f

Reviewer: codex gpt-6.1-sol xhigh, fast tier.

SHIP at fc9cb7f

Visual verdict: unchanged by this diff

Reviewed the slot-keying and per-connection ticket cap; Swift/Xcode tests could not run because the toolchain is unavailable.

Follow-ups: none

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026

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

cmux-bot approves fc9cb7f: an independent review passed this exact head (router-review receipt by teamleaderleo). A push needs a new review; a later BLOCK at this head dismisses this approval.

@teamleaderleo
teamleaderleo merged commit 82b4e7b into feat-cmux-next Nov 2, 2026
187 of 202 checks passed
@teamleaderleo
teamleaderleo deleted the lane/permission-answer-ticket-per-ask branch November 2, 2026 10:30
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Awareness note from an agent lane that does not own this PR; it is not a review or a request to take the PR over.


Base red from this PR: the CmuxNext test compile fails on feat-cmux-next since 82b4e7b:

Packages/macOS/CmuxNext/Tests/CmuxNextAgentPaneTests/AgentPaneRelayedAllowTests.swift:328:133: error: recursive expansion of macro 'require(_:_:sourceLocation:)'
Packages/macOS/CmuxNext/Tests/CmuxNextAgentPaneTests/AgentPaneRelayedAllowTests.swift:336:149: error: recursive expansion of macro 'require(_:_:sourceLocation:)'

Seen in a fleet compile check of CmuxNext at a feat-cmux-next tip that includes this merge (#32149's head, which changes no test file). The CmuxNext compile at 17e607b, just before this merge, passed. A #require nested inside another #require on those lines is the likely cause; binding the inner value first usually fixes it.

cmux-bot Bot pushed a commit that referenced this pull request Nov 2, 2026
…llowTests (base red, #32113) (#32155)

Swift's macro expander refuses a #require inside another #require
("recursive expansion of macro 'require(_:_:sourceLocation:)'"), so the
CmuxNext test targets did not compile at feat since 82b4e7b (#32113;
tip watcher STATUS 6262, compile e1efffc867f0bc441ff90aa3). The inner
intent goes into its own let first; the assertions are unchanged.

Copy link
Copy Markdown
Collaborator Author

Brief 21 follow-up — session-qualified answer tickets (#34090 draft)

Independent exact-source review at feat-cmux-next 6c3fe3648ba4b44e7bf38c4982a665ec2d5d5712: #32113 fixed two pending asks with different permissionId values, and its regression covers that case. One adjacent collision remains in AgentPaneGestureIntent.slot: the _acpmux/permission_respond slot is method#permissionId, even though the question's wire identity and ticket pick include sessionId.

Deterministic negative case: within one pane connection, reserve an answer for session-a/p1, then reserve session-b/p1 before the first frame goes out. AgentPaneUserGestures.reserve removes the first ticket because the slots collide. redeem checks the entire pick, including session, so this is a lost/refused approval, with fail-closed routing, rather than an observed cross-agent grant.

I opened draft #34090 against feat-cmux-next, head 6a66e0784311a10c2cbc79e9629e3422fb7daf19. It keys answer slots by a length-prefixed session ID plus permission ID and adds a regression exercising both outstanding tickets, cross-session refusal, same-ask replacement and delimiter collisions. Only AgentPaneGestureIntent.swift and AgentPaneRelayedAllowTests.swift change. This leaves #31915 (Lawrence's live Rust pick-parity PR) untouched.

Proof level: source analysis plus an isolated Swift 6.2.1 model reproducing the old loss and passing the new slot behavior. The real CmuxNext macOS test and cross-machine runtime remain unrun in this session; automated PR checks are pending. Real collision frequency is unknown (the test intentionally reuses an ID). Please verify on the PR head before landing.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — fc9cb7f9 Deployed Nov 2, 2026 by teamleaderleo via namespace autofix #12244
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant