Skip to content

[CMUX Next] Chief pet: the daemon refuses a reply once its agent left the foreground - #22044

Merged
teamleaderleo merged 55 commits into
feat-cmux-nextfrom
chief-pet-daemon-guard
Oct 20, 2026
Merged

teamleaderleo merged 55 commits into
feat-cmux-nextfrom
chief-pet-daemon-guard

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

The Chief pet's quick reply (#21738) checks with process-info that an agent, not a bare shell, is in the terminal's foreground, and then pastes the reply and presses Return. If the agent exits between that check and the paste, the shell runs the reply as a command. Now the daemon does the check itself, right before it writes.

send gets an optional expect_foreground string, behind the new capability send-expect-foreground-v1. Just before writing, the daemon reads the surface's foreground process-group leader again (foreground_process_name, the same value process-info reports as foreground_executable). If that leader isn't the named executable, or can't be read, the request fails with foreground changed: ..., classified as known-not-delivered, and nothing is written. expect_foreground only works with surface. Combined with terminal_id, the request fails with bad request, because a terminal that's still being created has no foreground yet.

On the App side, SendInputRequest and DaemonConnection.send gain expectForeground, and DaemonConnection.typeToForegroundProgram does the check and the two guarded writes, so the App target shrinks. typeChiefPetReply calls it and keeps its runningProgram check as the first gate, then passes the foregroundExecutable it saw on both writes, the paste and the Return.

Closes the follow-up coord-2 asked for when it reviewed #21738. Stacked on #21738: the base is feat/chief-pet-setting. Retarget to feat-cmux-next once #21738 merges.

Compatibility

The send command enum doesn't deny unknown fields, so an older daemon ignores expect_foreground and writes as before. The App's own check still protects that case, with the old small race. Swift leaves the field off when it's nil, so unguarded sends are byte-for-byte unchanged (tested). The spec and all seven generated bindings carry the new field (generate.py --write; --check, check-spec-inventory.py and check-resource-api-boundary.py pass).

Socket policy analysis

  • Can it run commands or open content on local objects? No new capability. send already writes input to a terminal, and this field can only make it refuse a write it would otherwise make. The remote relay still refuses send before parsing it (commands.md), so no relay path changes.
  • Can it change or destroy objects the caller doesn't own? No. It addresses the same surface with the same authority as before.
  • Does it read local state the caller shouldn't see? No. The comparison yields only match or mismatch, and the error text doesn't name the executable it found. Callers with send authority can already read it through process-info.
  • Phone lane: the generated DaemonLanePolicy+Fields now allows expect_foreground on send. The phone lane already has send and process-info, so this field adds no read, and it can only make the daemon refuse a write.
  • No command-bearing params, no new method, and no relay allowlist change.

Verification

  • Red first: a17e544 and 3707e5c add cli/send_expect_foreground.rs. Hosted run 39179652753 fails a_reply_for_an_exited_program_never_reaches_the_shell on Linux and macOS with the reply reached the shell: {"data":{},"id":4,"ok":true}.
  • Green: hosted run 39203646531 on ad26943 passes all three tests on Linux and macOS, plus clippy and fmt. The Rust SDK (cargo check -p cmux-sdk --all-targets) builds. A re-run on the merge head 8765203 is in progress.
  • Swift: new SendInputRequestTests (the encoded wire has expect_foreground, and an unguarded send omits it). No Swift toolchain on the authoring box, so these tests and the App change are compiled and run only by this PR's CI.
  • verify-local.py --affected: every selected check passes except localization, which fails with the base branch's existing needs_review catalog entries. This PR changes no strings.
  • No live dogfood: reproducing the race in the app depends on exact timing. The hosted tests drive the real daemon: sleep exits via Ctrl-C, then a guarded paste is refused and never reaches the shell.

Changelog

  • Fixed: A Chief pet quick reply no longer runs as a shell command when its agent exits just before the reply is typed.

🤖 Generated with Claude Code


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


Summary by cubic

Closes the race where a Chief pet quick reply was typed as a shell command when its agent exited between the app's foreground check and the paste. The daemon now verifies the foreground itself, right before it writes.

send gains an optional expect_foreground field behind the new send-expect-foreground-v1 capability. Just before writing, the daemon re-reads the surface's foreground process-group leader; if it isn't the named executable, or can't be read, the request fails with foreground changed: ..., writes nothing, and is classified as known-not-delivered. The app names the executable its existing process-info check saw on both the paste and the Return writes, and treats the capability as optional so an unguarded reply is still noted when an older daemon lacks it.

Older daemons ignore the field, leaving unguarded sends byte-for-byte unchanged. The field requires surface; combined with terminal_id the request fails as not delivered. The check is best effort: the program can still exit between the read and the write, a window of microseconds instead of a client round trip.

Tests

  • Hosted CLI tests drive a real daemon: a guarded paste is refused and never reaches the shell once sleep exits via Ctrl-C, and the foreground check covers a running program, terminal_id, and an unreadable foreground.
  • New Swift tests assert the guarded wire payload and an unguarded send carrying no field; all seven SDK bindings carry expect_foreground.

Written for commit 4cfeafc. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 30 commits October 18, 2026 14:23
A small Chief character in a non-activating panel that joins every Space,
never takes focus, drags anywhere (its spot is kept per display and pulled
back on screen), and opens the Chief on a click. Behind the chiefPet.enabled
Debug Settings toggle, off by default, until it has a user setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CmuxNextApp may not grow (code-quality SW14), so the pet lives in a new
CmuxNextFloating target with the floating chat's per-display frames, which it
already shares; the App keeps only the wiring and shrinks by 23 source lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A public default argument may only use what the file imports publicly; the
controller now takes an optional switch and reads the tunable itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The App's ChiefAvatar.png is the round cat Home draws in a disc; Leo's
references float the full-body Chief the Chief Dock uses. CmuxNextFloating now
bundles that character (ChiefPet.png, the dock's ChiefAvatar.png) as the pet's
default image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Match chief-water-reference: the Chief Dock's pixel mascot (Orb by default,
all nine bundled, scaled up with whole pixels) over a glass rail with a
compose tool, 8 pt apart. A drag anywhere but a tool moves the pet; a click
on the character or compose opens the Chief.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rail matches chief-water-reference's capsule: compose and the chevron
(the dock's disclosure mark pointing up). The character's box is 88 x 80 pt
so the pixel mascot carries the reference pet's weight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What alerts on this Mac (a banner's rules) also shows over the pet, matching
chief-chat-source-captures d154d558: glass capsules of the references' size
(330 x 56 pt) with a bold title and one muted line and no status glyph, the
older ones peeking out below as a deck; the rail's chevron unfolds them into
a column and each capsule opens its tab. One notice per tab; identical
content never re-animates; a finished turn leaves after 8 s while a question
or failure stays, counted on the character's badge, until its tab is read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A click on the pet or the rail's compose opens a reply capsule just over the
pet, the notices' size and glass, that takes the keys without activating
cmux. It starts on the newest notice: Return types the reply into that
terminal (as a notify --reply banner does) or, for an agent chat, opens the
chat in the floating chat or its tab; with no notice it messages the Chief.
Cmd-Return opens the target. The Quick Agent Chat's panel moves into
CmuxNextFloating with the rest of the floating surfaces, so the App keeps
shrinking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ettings

home.chiefPetEnabled (off by default) and home.chiefPetMascot (the Chief
Dock's nine pixel mascots, Orb by default) in Settings > Home > Chief Pet,
the palette's settings rows and cmux settings set replace the Debug Settings
toggle; agents may set both (looks only). The pet follows them live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Replies type into a terminal only while the notice is still its tab's
  unread notification, as a notify --reply banner's do.
- A notice read anywhere else (another client, a closed tab) leaves the pet.
- The reply menu keeps two notices with one title apart, keeps the chosen
  session across refreshes and falls back to the Chief, never to another
  session; Cmd-Return opens, other command keys stop at the box.
- Notice capsules take their press, so a click opens the tab; an agent
  chat opens its tab rather than a second copy in the floating chat.
- A finished turn dropped behind three waiting notices shows nothing.
- CmuxNextFloating no longer imports CmuxNextDaemon (the App maps the
  notification); the pet is made by the notification service, so
  AppServices and AppDelegate stay unchanged; the mascot constant is
  initial (lint); the Chief Pet settings get a card under Agents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pet-setting

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextSettings/Schema/SettingsPageLayout.swift
…plies, no screen sharing

- home.chiefPetEnabled is user-only: the pet puts agent output over every app
  and a reply box that types into terminals. The character stays agent-settable.
- A pet reply types only while a program still runs in the terminal's
  foreground: process-info and the paste go over one connection, back to
  back, and a bare shell refuses the reply.
- The pet, its notices and the reply box stay out of screen shares.
- The notice deck takes its size with no screen, and the deck height test
  compares CGFloats.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r the Chief pet

Generated only. Settings exports from the fleet export-settings step at
2f2f1e6 (cmux-ci step 20d9ccba77afe7b51e35de31, decoded from its
BEGIN_ARTIFACT blocks), defaults snapshot from defaults_guard.py snapshot,
Settings page strings from webviews/scripts/pages/gen-strings.mjs settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pet-setting

# Conflicts:
#	schemas/settings-export/settings-schema.sources.sha256
#	schemas/settings/settings-schema.json
…ef pet test

feat-cmux-next moved NotificationPolicy into CmuxNextSettings (#21735).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…base merge

Generated only, from the fleet export-settings step at 492302f
(cmux-ci step on key chiefpet-export-settings-492302f); defaults snapshot and
Settings page strings checked fresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pet-setting

# Conflicts:
#	Packages/macOS/CmuxNext/Package.swift
send with expect_foreground is ignored today, so a Chief pet reply typed
after its agent exited runs as a shell command. This test is red until the
daemon checks the foreground in the send.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pet-setting

# Conflicts:
#	Packages/macOS/CmuxNext/Package.swift
#	Packages/macOS/CmuxNext/ci-target-graph.json
#	schemas/settings-export/settings-schema.sources.sha256
#	schemas/settings/settings-schema.json
…base merge

Generated only, from the fleet export-settings step at b6e4dd2
(key chiefpet-export-settings-b6e4dd2); defaults snapshot and Settings page
strings checked fresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pet-setting

# Conflicts:
#	schemas/settings-export/settings-schema.sources.sha256
#	schemas/settings/settings-schema.json
…base merge

Generated only, from the fleet export-settings step at 5bedbf4
(key chiefpet-export-settings-5bedbf4); defaults snapshot and Settings page
strings checked fresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pet-setting

# Conflicts:
#	schemas/settings-export/settings-schema.sources.sha256
#	schemas/settings/settings-schema.json
…base merge

Generated only, from the fleet export-settings step at c1475df
(key chiefpet-export-settings-c1475df); defaults snapshot and Settings page
strings checked fresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo and others added 3 commits October 19, 2026 19:39
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated SDK bindings regenerated with cmux-tui/bindings/codegen/generate.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…const

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change reaches terminal input in the Chief pet workflow and adds an optional send contract for protocol clients.

The PR should not merge yet because a guarded reply can still reach the shell after the terminal-host connection waits or queues input.

Findings

  1. P1 Replies still reach the shell ▶

Summary

This PR adds an optional foreground check to terminal input.

  • A guarded terminal send stops when its named program leaves the foreground.
  • Chief pet replies carry the checked program through both writes.

Diagram

sequenceDiagram
  participant App
  participant Daemon
  participant Host as Terminal host
  participant PTY
  App->>Daemon: process-info
  Daemon-->>App: Foreground executable
  App->>Daemon: send with expect_foreground
  Daemon->>Daemon: Compare current foreground
  alt Foreground differs or cannot be read
    Daemon-->>App: Refuse without writing
  else Foreground matches
    Daemon->>Host: Send input bytes
    Host->>PTY: Write input
  end
Loading

Reviews (5) · Last reviewed commit: "cmux NEXT: the guarded pet reply lives o..." · Reviewed by Greptile

Comment on lines +125 to +126
if let Some(expected) = expect_foreground {
let found = surface.process_id().and_then(platform::foreground_process_name);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Replies still reach the shell

The foreground check runs before the terminal host receives the input. write_paste and write_bytes send bytes over another connection. send_hosted_input can wait up to five seconds for that connection to recover, then retry without checking the foreground again. If the agent exits during that wait, the reply reaches its shell despite expect_foreground.

Even without reconnecting, both quick-reply requests can pass their checks before the host consumes their queued frames, letting the shell run the reply as a command. Carry the expectation to the PTY owner and check it after any wait, immediately before writing.

Knowledge Base Used: TUI client and relays

teamleaderleo and others added 5 commits October 19, 2026 14:08
…pet-setting

# Conflicts:
#	schemas/settings-export/settings-schema.sources.sha256
…h (Linux autofix's output)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same 15pt title size and semibold weight, now from TypeScale, so the
Type scale check no longer flags the pet's files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The app lists send-expect-foreground-v1 as an optional capability and
  notes an unguarded reply when an older daemon lacks it.
- expect_foreground with terminal_id is refused as not delivered, like
  the other send refusals.
- The spec says the check is best effort, with a residual window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Correctness-first review (opus subagent) at f336cfc: SHIP with nits, no blockers. Each nit is fixed at ee8ad50:

  1. NotificationCenterService+ChiefPet.swift never checked send-expect-foreground-v1, so against an older daemon the guard silently did nothing. Now the app reads the capability and notes the reply as "(unguarded)" when the daemon lacks it. The reply still goes, checked one round trip early as before, so older Cloud daemons keep working. The capability is in DaemonCapabilities.optional and plans/cmux-next/daemon-capabilities.json; check-app-capability-references.py passes.
  2. send_expect_foreground.rs had no case for a foreground that can't be read. New test a_reply_is_refused_when_the_foreground_cannot_be_read: the shell exits, then a guarded send must return ok: false. It's committed before the fixes. It hasn't run locally (no cargo on this Mac, and Huge Blue is unreachable), so this PR's cmux-tui CI is its first run.
  3. cmd_terminal_io.rs: expect_foreground with terminal_id used a plain bail!. It's now known_not_delivered, like the other send refusals.
  4. spec/commands.md: added that the check is best effort, with a window of microseconds between the read and the write.

teamleaderleo and others added 2 commits October 19, 2026 15:20
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ef-pet-daemon-guard

# Conflicts:
#	cmux-tui/bindings/cpp/.cmux-sdk-manifest.json
#	cmux-tui/bindings/cpp/include/cmux/raw/generated/models.hpp
#	cmux-tui/bindings/go/raw/.cmux-sdk-manifest.json
#	cmux-tui/bindings/go/raw/generated_commands.go
#	cmux-tui/bindings/go/raw/generated_events.go
#	cmux-tui/bindings/go/raw/generated_metadata.go
#	cmux-tui/bindings/go/raw/generated_presence_test.go
#	cmux-tui/bindings/go/raw/generated_types.go
#	cmux-tui/bindings/java/src/com/cmux/raw/.cmux-sdk-manifest.json
#	cmux-tui/bindings/java/src/com/cmux/raw/Protocol.java
#	cmux-tui/bindings/python/cmux/raw/_generated/.cmux-sdk-manifest.json
#	cmux-tui/bindings/python/cmux/raw/_generated/_schema.py
#	cmux-tui/bindings/python/cmux/raw/_generated/metadata.py
#	cmux-tui/bindings/rust/src/generated/.cmux-sdk-manifest.json
#	cmux-tui/bindings/rust/src/generated/commands.rs
#	cmux-tui/bindings/rust/src/generated/events.rs
#	cmux-tui/bindings/rust/src/generated/metadata.rs
#	cmux-tui/bindings/rust/src/generated/mod.rs
#	cmux-tui/bindings/rust/src/generated/types.rs
#	cmux-tui/bindings/typescript/src/raw/generated/.cmux-sdk-manifest.json
#	cmux-tui/bindings/typescript/src/raw/generated/commands.ts
#	cmux-tui/bindings/typescript/src/raw/generated/events.ts
#	cmux-tui/bindings/typescript/src/raw/generated/index.ts
#	cmux-tui/bindings/typescript/src/raw/generated/metadata.ts
#	cmux-tui/bindings/typescript/src/raw/generated/types.ts
#	cmux-tui/bindings/zig/src/raw/generated/.cmux-sdk-manifest.json
#	cmux-tui/bindings/zig/src/raw/generated/protocol.zig
Base automatically changed from feat/chief-pet-setting to feat-cmux-next October 20, 2026 00:30
…aemon-guard

# Conflicts:
#	Packages/macOS/CmuxNext/Package.swift
#	Packages/macOS/CmuxNext/Sources/CmuxNextApp/Notifications/NotificationCenterService+ChiefPet.swift
#	schemas/settings-export/settings-schema.sources.sha256
#	schemas/settings/settings-schema.json
@cursor

cursor Bot commented Oct 20, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 20, 2026 00:36 — with GitHub Actions Inactive
… lane knows expect_foreground

typeToForegroundProgram does the foreground check and the two guarded
writes in CmuxNextDaemon, so the App target shrinks. The regenerated
phone-lane field list allows expect_foreground on send: the phone lane
already has send and process-info, and the field can only refuse a write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 20, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 20, 2026 00:42 — with GitHub Actions Inactive
@teamleaderleo
teamleaderleo merged commit 0180615 into feat-cmux-next Oct 20, 2026
@teamleaderleo
teamleaderleo deleted the chief-pet-daemon-guard branch October 20, 2026 00:42
teamleaderleo added a commit that referenced this pull request Oct 20, 2026
The Chief pet stack this PR sat on landed as #21738 (and #22044), so the
stack files take the base copy; this keeps only the PR's own three commits
(0dae381..383d2f6) on top. Generated exports take the base copy for
the autofix to regenerate.
@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

This branch was previously deployed

2 inactive (1 outdated) deployments
cmux-tui-merge-tree — 4cfeafcb Deployed Oct 20, 2026 by teamleaderleo
artifacts — 5608553d Deployed Oct 20, 2026 by teamleaderleo
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