Chief: typed approval answers read loosely; allow all, for this turn or for 1h

Closed
TE
15d ago
+525-26

Summary

Leo answered a Chief approval from his phone with "allow", "allow..." and "ALLOW", and the Chief kept waiting. Only a bare allow/deny counted, each answer settled one card, and a single turn sent six cards.

  • A typed answer ignores case, punctuation and emoji. allow, ALLOW..., yes, ok, ๐Ÿ‘ and โœ… allow; deny, no, ๐Ÿ‘Ž and โŒ deny. Any other word in the message ("yes, and check CI", "hello?") keeps it a normal message.
  • allow all / deny all answer every pending card in that conversation.
  • allow for this turn also lets the rest of the running turn go through. It is cleared when the turn ends or when another participant's message is steered into the turn.
  • allow for 1h (also 30m, 2 hours, an hour; at most a day) also covers the owner's own gated turns (paired device, Home) until it expires, never another participant's turn.
  • One line confirms what was answered, e.g. "Allowed: run ls" or "Allowed for 1h: run ls".
  • The Chief's own send and control status are described in words ("send you โ€œโ€ฆโ€ with a file from this Mac", "check on its subagents"), not as tool names or JSON.

The rest of the brief was already on base in #22640. Paired-device turns no longer ask by default (remote.ask is off, so they run like local turns). The Chief's own send/reply/status never ask. The card text is human-readable. The screenshot came from a Chief binary built before #22640: its "Approval needed: โ€ฆ every local effect waits for you โ€ฆ wants to run: {json}" text no longer exists in the tree.

Security: nothing new runs without the owner's word. Scoped allows are set only by the owner's own message (is_owner_message), stop/deny-repeat rules still win, a time window never covers another participant's turn, and deny never carries forward. Risk is normal: this changes how the owner's explicit answers are parsed. It does not change which turns are gated or who can answer.

Restarts that cut turns (brief item 4) were handed to lane update-survival with evidence: ChiefAcpmuxHeal.heal() โ†’ handOffReplaced() sends SIGTERM to the host right away after an in-place update, instead of handing off when the turn is idle.

Testing

Red: cargo test --manifest-path Native/OptChat/optchat-chief/Cargo.toml --test approval_gates at cb5e16f (test-only commit). It cannot compile there: approval::Reply/Scope don't exist yet, and the send/status descriptions differ. Green: the same command at the head, run by the optchat-chief tests (real daemon) job.

Observed / Not run

  • Observed: rustfmt on the changed files.
  • Not run locally: cargo build/test/clippy. Agents don't compile on the Mac, and the fleet ci-step allowlist refused scripts/ci/cargo-test-selected.sh, so this PR's CI job is the first compile and test run. The red result above is by construction; it was not executed.
  • The commits were made through the GitHub git-data API because local git on cosmic-gray was stuck in I/O wait, so the pre-push gate did not run. The change touches only Rust and README, which the gate's Swift checks don't cover.

Changelog

Fixed: Chief approvals accept "allow", "ALLOW...", "yes" or ๐Ÿ‘ typed from a paired device, plus "allow all", "allow for this turn" and "allow for 1h", and confirm what was allowed.

Agent session: cosmic-gray

๐Ÿค– 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

Fixes Chief approval answers so a typed reply from a paired device is read loosely and can cover more than one pending request. Previously only a bare allow/deny counted, one answer settled one card, and a single turn sent six cards.

  • A typed answer ignores case, punctuation and emoji; any other word in the message keeps it a message.
  • allow all / deny all answer every pending approval in that conversation.
  • allow for this turn lets the rest of the running turn run, but only when the answer is to the turn's own request (a subagent's leftover card does not count); it is cleared when the turn ends or another participant joins.
  • allow for 1h (also 30m, 2 hours, an hour; durations add up, at most a day) covers the owner's own gated turns (paired device, Home) until it expires, never another participant's; a zero window is not an answer.
  • One line confirms what was answered, and the Chief's send and control status are described in words, not as tool JSON.

Security: nothing new runs without the owner's word; scoped allows are set only by the owner's own message, stop/deny-repeat rules still win, and a time window never covers another participant's turn.