Repository navigation
Description
This was generated by AI during triage.
Problem
Codex reports OSC 7501 program status without first sending the support probe (ESC ] 7501 ; ?), so Workroom shows its marks but never alerts for it. A waiting Codex terminal gets the ✋ mark, but no "needs you" notification, and it isn't counted in the Dock badge or the menu bar.
This is not a Codex bug. The spec (revision 0.4) makes the probe optional:
Detection is optional. Terminals MUST ignore unknown OSCs, so a program MAY send reports without querying first, for example when it cannot read replies.
It doesn't tie notifications to the probe either: "A terminal MAY produce a notification when a record changes", and "Terminals SHOULD rate-limit anything a record causes outside the terminal, such as desktop notifications or sounds." So gating alerts on the probe is Workroom's own rule, which exists so that a cated recording can't raise alerts (docs/program-status.md, "Only a program that asked … can alert you").
- Codex: from Report terminal program status with OSC 7501 openai/codex#52725 (merged 2026-10-09) it writes
ESC]7501;state=<state>:app=codex ESC\whenever stdout is a terminal. Report terminal completion states and blocking reasons openai/codex#54114 (2026-10-14) addeddone,errorandkind=permission|question|auth. - Workroom: wr-agent sets a record's
notifyonly when the reporting process group probed (notify: self.probed == Some(owner)in the program-status store). The app skips alerts for a record withoutnotify(ProgramStatusAlerts) and leaves it out of the waiting count (counted: record.notify && enabled).
Reproduced 2026-10-16: Codex 0.163.0 run as a session's command under a scratch wr-agent serve sent state=idle and then state=working with no probe, and wr-agent program-status showed app="codex" … notify=false. notify follows the process group, not the state, so a blocked record from Codex is notify=false too. A real permission prompt was not driven (it needs an API turn).
No report or request about this exists on openai/codex (searched 2026-10-16).
Direction
Fix it in Workroom: let a program that didn't probe alert once it has shown that it is a running program and not a replay, by its process group still being alive a short time after it reported blocked. The brief is in the comments.
Context: #358 (program status), ADR 0050, #361 (closed; hooks rejected, .out-of-scope/agent-lifecycle-hooks.md).
🤖 Left by Claude Code.
Activity
docs: record why agent lifecycle comes only from OSC 7501
joelmoss commentedon Oct 16, 2026
Superseded by the revised brief: #481 (comment)
🤖 Left by Claude Code.
joelmoss commentedon Oct 16, 2026
Agent Brief
Category: enhancement
Summary: Let a program that doesn't probe (Codex) raise "needs you" alerts once it has shown itself live, and make every waiting request produce exactly one notification.
This supersedes the earlier brief on this issue. The decisions below were settled with the maintainer in a grilling session on 2026-10-16.
Current behavior:
notifyis true only when the agent answered a support probe from the record's owner group (the pty's foreground group when the report arrived). Codex never probes; the spec allows that ("a program MAY send reports without querying first"). So every Codex record hasnotify=false, it never alerts, and it never counts as waiting.ProgramStatusAlerts.applytreats a blocked record withnotify=falseas already notified the first time it sees the episode, so a later change can't alert either.Store::drop_dead_owners,group_alive) only on a pty read (Shadow::write_from_pty), never on a timer.ProgramStatusAlerts.absorbsOSCfolds a program's own desktop notification (any OSC notification) into the "needs you" alert only if that alert has already been raised, the session has exactly one blocked record, and the notification arrives withinmergeWindow(10 s) of the request starting. Codex writes its own OSC 9 just before its OSC 7501 blocked report (in its redraw,maybe_post_pending_notificationruns before the draw that refreshes program status), and the two reach the app by different routes (surface action vs the agent's program-status service). So a Codex request shows two notifications: Codex's, then Workroom's.Desired behavior:
Alerts for a program that didn't probe
blockedrecord whose owner group didn't probe becomesnotify=truewhen, at the agent's existing screens tick (every 2 s), at least 1 s has passed since the report, the owner group is still alive, and it is still the pty's foreground group. The alert therefore lands 1–3 s after the request starts. The agent publishes the change as a normal record update.notify=truewithout a probe.catcase.notify=trueat once.notify=falsetonotify=truealerts then, under the existing rules (once per episode, the per-tab cooldown, no alert while an attachment of the session has focus), and starts counting as waiting.One notification per request
Records
docs/program-status.mdis updated in the same PR. That includes quoting the spec's "MAY send reports without querying first", and adding thetail -flimit below to Known limits.Key interfaces:
notifyflag and how it is set (Store::apply_read/report, theprobedgroup); unprobed blocked records need a "reported at" (or equivalent) for the grace check.group_alive, the pty's foreground group (foreground_pgid) and the session's child pid.notifychange is a record update the app must act on.ProgramStatusAlerts:apply(the!record.notifybranch),absorbsOSC(hold, fold-any-time, newest-request), and withdrawing a posted ordinary notification for the backstop.ProgramStatusLimits: the hold (~500 ms) and backstop lookback (5 s) belong besidemergeWindow.Acceptance criteria:
notify=trueat the first tick ≥1 s after the report; one whose group dies first, stops being foreground, or is followed by a prompt staysnotify=false.notify=true.notify=trueimmediately.notify=falsethen updated tonotify=truein the same episode raises exactly one alert and enters the waiting count. With the session focused, it raises none.cating a file holding a blocked report, in a terminal you aren't looking at, still raises no "needs you" alert.docs/program-status.mdupdated as above.Out of scope:
tui.notifications).doneorerror.app=codexby name).shortcut:inapply_read(probe and report landing in different reads of one replay).TERM_PROGRAM=ghostty).Accepted limit: a long-running foreground program that prints someone else's blocked report (
tail -forless -Ron a raw recording) can raise one alert. The rule cannot tell a replay from a relay (ssh,mosh,tmux), and relays are the case it must serve.Left to the maintainer at review: a manual check with a real Codex permission prompt in the running app, with the terminal unfocused. Expect exactly one "needs you" notification carrying Codex's text, the Dock count, and ⇧⌘M reaching the terminal. Agents must not run UI tests unasked.
🤖 Left by Claude Code.
joelmoss commentedon Oct 16, 2026
Agent Brief: addendum (eng review, 2026-10-16)
An engineering review of the brief above, with a second opinion from Codex, found the gaps below. Each amends the brief and is part of the contract. Where this addendum and the brief differ, this addendum wins.
R1. The app must notice a later
notifyflip.ProgramStatusAlerts.applyonly visits episodes it hasn't opened yet, so a record whosenotifygoes from false to true on an already-open episode is never looked at.applymust re-examine open episodes whose record changed tonotify=true, and alert under the existing rules.R2. Promotion sticks for the whole episode.
Store::reportkeeps a blocked record's episode on a repeated report, but recomputesnotifyfrom the probe alone. A promoted record would drop back tonotify=false. Codex re-sends its current state after returning from an external program, so this happens in practice. The grace clock starts when the episode starts, not on each report. A promoted record stays promoted while the same owner re-reports the same blocked episode.R3. "The shell's group" means the pty's child only in an interactive-shell session.
The app always starts sessions with an empty command, so in production the child is always the shell. A session with a command runs the program itself as the child (
sh -c "exec …"), which test harnesses do. The exclusion applies only when the session has no command.R4. The alert carries the program's own text when the record has none.
Codex's OSC 7501 reports carry
state,appandkindonly, with nomsg. The detail ("Approval requested: ") is only in Codex's own OSC 9 notification.NotificationCenterStore.mergeonly fills an empty body, and a "needs you" body is never empty. So on the hold path, build the alert before posting it, using the program's notification text as its quoted detail when the record has no message. A notification folded in after the alert keeps today's merge rule.R5. A raised episode counts as waiting straight away.
The waiting count uses
record.notify && enabled. An alert raised on the hold path still hasnotify=falseuntil the agent promotes it, so it would be missing from the Dock count. An episode counts while it is raised ornotify=true.R6. Accepted limit: a replayed recording that holds an OSC 9.
On the hold path,
cating a raw recording that contains both a program notification and a blocked report, in an unfocused pane, raises a brief "needs you" alert. It is withdrawn when the shell's next prompt drops the record. The same replay already posts an ordinary notification today. Won't fix. Narrow the brief'scatacceptance criterion to a file with no program notification, and add this case to Known limits.R7. State the one-notification guarantee as it really is.
On a lagging remote link, a banner and sound can already have been delivered before the backstop withdraws the duplicate. A program that sends its own notification before its first OSC 7501 report is not held. Codex and Claude Code both report
idleat startup, so neither is affected. ADR 0052 anddocs/program-status.mdsay: one notification in normal operation; on a lagging link a duplicate may show briefly and is then removed.Additional acceptance criteria:
notify=falsetonotify=truealerts exactly once.notify=true.Build order: two independent lanes (the agent's promotion, and the app's alerts), then the ADR and docs. One PR.
🤖 Left by Claude Code.
fix: pre-landing review fixes (#481)
docs: a prompt that appears while the terminal is focused never alerts (
feat(wr-agent): let a program that never probes alert once it stays r…
feat(macapp): one notification per request, and alerts for unprobed p…
docs: ADR 0052 and the program status guide for unprobed programs (#481)
fix(macapp): fold into the newest open request and never swallow a he…
test(wr-agent): keep the promotion test's reporter alive past a loade…
fix: pre-landing review fixes (#481)
docs: a prompt that appears while the terminal is focused never alerts (