Skip to content

OSC 7501 — Program Status ​

SequenceOSC 7501 ; state=<state> [ : <key>=<value> ]... ST
SpecProgram Status Protocol (rev 0.4)
Otty support✓

Description ​

Lets a program tell the terminal what it is doing: resting (idle), running (working, optionally with a percentage), stopped on you (blocked — a permission prompt, a question, a password), finished with a result you haven't seen (done), or failed (error). A short message says it in the program's own words.

Where OSC 9;4 only carries a progress bar, OSC 7501 carries why the program needs you, and one program can report several things at once — a deploy can have one region pushing at 40 % while another waits for approval.

Where it shows up ​

There is nothing to turn on: as soon as a program in a pane sends a report, Otty shows its most urgent record (see Which record is shown) in four places:

WhereWhat you see
Tab badgeworking → running spinner · blocked → awaiting-input badge · error → error badge · done → checkmark on the tab you're on, unread dot on any other tab · idle / no records → the badge clears. See Progress State → Tab badges.
Dock iconworking spins the icon when Animate on Progress is on (off by default); error tints it red when Red on Error is on (on by default). Both are in Settings → Appearance → Dock Icon. See Progress State → Dock icon.
Inbox and tab previewThe record's message becomes the tab's line in Inbox (the bell beside + in the titlebar, or View ▸ Inbox) and in the card you get by hovering a tab — ahead of an agent's step or the running command. A blocked record lists the tab with the ones that need you; a working one with the running ones. Once a coding agent runs in the pane, a settled (done / error / idle) message gives way to the agent's own line.
NotificationsA new blocked, done or error report posts a banner — see Notifications.

The badge rules are the same as for OSC 9;4 and coding-agent hooks, and a badge another source put up (an agent's hooks, shell integration, OSC 9;4, otty tab badge) is never replaced by a program-status one.

Notifications ​

A banner fires when a report brings a new blocked, done or error record — unless you're already looking at that tab (it's the active tab of the front window and Otty is the active app). At most one banner per pane every 3 seconds; extras are dropped, not queued.

The banner's title is the record's title, else its app, else the pane's title; its body is the msg (followed by the pane's name), or a stock line such as Needs permission, Has a question, Failed or Finished when there is none.

Because the text is chosen by the program, a banner needs Allow App Notifications on (Settings → Shell → Notification, on by default) and the toggle for its state:

StateToggleDefault
blockedNotify When Awaiting Input (Settings → Agents)on
errorNotify on Error Exit (Settings → Shell → Notification)on
doneNotify on Command Finish (Settings → Shell → Notification)off

The blocked badge likewise follows Badge When Awaiting Input (Settings → Agents), and the unread dot for done follows When Command Finishes (Settings → View → Tab Icon & Badge); both are on by default. See Privilege and Notifications for sounds and the Dock bounce.

Wire format ​

OSC 7501 ; <key>=<value> [ : <key>=<value> ]... ST
  • ST is ESC \ or BEL. Pairs are separated by :; keys are lowercase ASCII; values use only A–Z a–z 0–9 _ . , + / = -.
  • Unknown keys are ignored, a malformed pair is skipped, and a repeated key takes its last value.
  • The whole sequence is at most 4096 bytes.
  • A report that breaks a rule below — a missing or unknown state, a bad id, a field over its limit, invalid base64, non-UTF-8 text or a control character — is discarded whole.

Fields ​

KeyValueMeaning
stateidle · working · done · blocked · error · clearRequired. The record's state, or clear to remove records.
idseg/seg/…Which record. Absent = the root record. Segments are A–Z a–z 0–9 _ . + -, 1–32 characters each, at most 8 deep and 128 characters in all.
kindpermission · question · authWith blocked only: what the program waits for. Ignored with any other state.
progress0–100With working / blocked only. Absent = indeterminate.
appplain text, ≤ 32The program's name. A record without one takes its nearest parent's.
titlebase64 UTF-8, ≤ 192 bytesA short label for the record (e.g. a region name).
msgbase64 UTF-8, ≤ 2048 bytesWhat the program is doing, in its own words.

title and msg are shown as plain text, never markup. Bidi overrides and invisible formatting characters are stripped, so a message can't disguise what it says.

States ​

stateMeaningLasts until
idleAt rest, nothing in flightReplaced or cleared
workingRunning; progress if knownThe program ends (see Lifetime)
blockedWaiting on the user; kind says for whatThe program ends
doneFinished, result not yet seenReplaced or cleared
errorFailedReplaced or cleared
clearNot a state: removes the record named by id and every record beneath it; with no id, removes all of them—

A report replaces its record whole: a field you don't repeat is gone (send progress again on every working update).

Which record is shown ​

Each pane keeps up to 256 records (the least recently updated one goes first). The badge, Dock, Inbox and preview show one of them: the most demanding state — blocked > error > working > done > idle — and among equals, the most recent report.

Lifetime ​

working and blocked describe a live program, so Otty drops them when the program is gone:

  • A new shell prompt — with shell integration, Otty's own prompt mark; without it, any OSC 133;A.
  • The command ends in a nested shell — e.g. inside ssh host, records reported while a remote command ran go when that command finishes.
  • The pane's process exits.

done, error and idle stay until the program replaces or clears them, so a finished result is still there when you come back. A full reset (RIS, ESC c) removes every record; a soft reset or a switch to or from the alternate screen keeps them.

Feature detection ​

OSC 7501 ; ? ST

Otty answers with every state and kind it supports:

OSC 7501 ; ? : states=idle,working,done,blocked,error : kinds=permission,question,auth ST

Send the query followed by a Primary Device Attributes request (CSI c). Otty replies to the query first; if the DA reply comes back without a 7501 reply ahead of it, the terminal doesn't support the protocol.

Example ​

bash
b64() { printf '%s' "$1" | base64; }

# Working, indeterminate
printf '\e]7501;state=working:app=brew:msg=%s\e\\' "$(b64 'Installing updates')"

# Waiting for a password
printf '\e]7501;state=blocked:kind=auth:app=brew:msg=%s\e\\' "$(b64 'Password required to install updates')"

# Finished — stays until replaced or cleared
printf '\e]7501;state=done:app=brew:msg=%s\e\\' "$(b64 '12 packages upgraded')"

# Remove every record
printf '\e]7501;state=clear\e\\'

Several records at once — a deploy with one record per region under a root that names the app:

bash
printf '\e]7501;state=working:app=deploy\e\\'
printf '\e]7501;state=working:id=us-east:title=%s:progress=40:msg=%s\e\\' "$(b64 'US East')" "$(b64 'Pushing image')"
printf '\e]7501;state=blocked:id=eu-west:kind=permission:msg=%s\e\\' "$(b64 'Approve production rollout')"
# The tab shows eu-west (blocked outranks working), with app "deploy" inherited from the root.

printf '\e]7501;state=clear:id=eu-west\e\\'   # drops eu-west and anything under it

Write to the terminal, not a pipe

Emit to /dev/tty when your stdout may be captured (piped, or run under another tool), so the sequence still reaches the terminal.

Otty