Repository navigation
[Bug]: False "Task complete" notifications while the agent is still working (agent started after the setup script finishes) #26014
Description
Operating system
macOS 26.6.2 (25G83), Apple Silicon (arm64)
Orca version
1.4.221 (build e65bbc95cec14b365e5cb22e3fdfdff413c1f82b)
Agent
OMP (oh-my-pi) 18.6.1. The login shell is zsh. Orca's setup sequence ran under Homebrew bash 5.3.15.
Details
Short summary
When a worktree has a setup script and the agent waits for it, Orca starts the agent through bash -lc 'eval "$ORCA_SEQUENCED_STARTUP_SCRIPT"' after setup finishes. That bash has no job control. The agent therefore runs inside bash's process group, and the terminal's foreground process group leader is bash, not the agent.
Orca's terminal daemon then reads the pane as "foreground is bash, no child processes" whenever its cached agent identity is older than 1 s. A hidden pane is polled every 3 s, so every poll reads that stale result. Orca concludes the agent has exited and posts a generic notification while the agent is still working:
Task complete in <worktree>
<repo> · omp
I see this with OMP. Any TUI agent started this way is probably affected, but I only checked OMP.
What happened
- On 1.4.221, I got 13 generic
Task complete in <worktree>/<repo> · ompnotifications in about 75 minutes. All 13 came from the 3 panes whose agent was started by the setup sequence. - There were none from 9 other OMP panes in the same period. In those panes OMP had been started at the zsh prompt, so it led its own process group.
- Each time, the agent was still working: either mid-turn or waiting on background subagents.
- The notifications appear after I switch away from the worktree. While I am in it, delivery is suppressed (
suppressed-focus,notification-delivery-service.ts:139). - On 1.4.218 this happened about once per new session. On 1.4.221 it repeats within the same session.
How to reproduce
- On macOS, use a repo with a setup script, and make the agent wait for setup to finish.
- Create a worktree with OMP as the agent, and give it a task that runs for more than ~30 s.
- Switch to another worktree.
- A
Task complete in <worktree>notification arrives within a few seconds while the agent is still running.
To confirm the cause:
$ ps -o pid,ppid,pgid,tpgid,stat,comm -t <pane tty>
zsh (pgid A)
bash (pgid B, tpgid B) <- foreground group leader
omp (pgid B) <- agent, not a group leader
I also queried the daemon read-only over its control socket. I used a separate client ID, sent inspectProcess and getForegroundProcess, and kept the agent process alive and working throughout:
| Pane | Read every 3 s (hidden poll rate) | Read every ~0.8 s |
|---|---|---|
| Agent started by the setup sequence | always foregroundProcess: "bash", hasChildProcesses: false |
alternates between bash/false and omp/true |
| OMP typed at the zsh prompt (control) | always omp/true |
always omp/true |
Code path (at e65bbc9)
src/shared/setup-agent-sequencing.ts:134: the success branch runseval "$ORCA_SEQUENCED_STARTUP_COMMAND"; exit "$?". In a non-interactive bash without job control, the agent stays in bash's process group. Theexecform at:157is used only as a fallback.src/main/daemon/pty-subprocess/foreground-process-tracker.ts:33,244-265: the node-pty name is the group leader,bash. A recognised agent is cached forFOREGROUND_AGENT_CACHE_TTL_MS = 1000and refreshed only asynchronously, on reads. Once the TTL expires, a POSIX shell fallback is returned unchanged. The Windows branch already keeps the cached agent for a shell fallback (process.platform === 'win32' && isShellProcess(fallbackProcess)).src/main/daemon/terminal-host-process-inspection.ts:127-139:hasChildProcessesis derived from that shell name, so it isfalse. In my panes, the evidence verdict wasunverifiablewith reasontty_boundary(agent-foreground-process-remote-evidence.ts:78), so it did not correct the result.src/renderer/src/components/terminal-pane/agent-completion-poll-cadence.ts:6andagent-completion-poll-scheduler.ts:50-51: a hidden pane polls every 3000 ms. That is longer than the 1 s cache, so every hidden poll readsbash.src/renderer/src/components/terminal-pane/agent-completion-inspection-result.ts:148-182: two misses more than 1.5 s apart dispatchprocess-exitwith the titleomp.src/renderer/src/components/terminal-pane/use-notification-dispatch.ts:109-115: aprocess-exitdrops a hook status that is notdone. The notification therefore has no agent fields, and falls back to the generic shape.- Probably related to why it is more frequent on 1.4.221: since fix(opencode): report OpenCode 2 status from each pane's own TUI #23722,
agent-completion-hook-observer.ts:119-121callsclearProcessExitCompletion()on every hookworking. This appears to remove the old once-per-pane dedupe. I inferred this from the code and did not A/B test it.
Suggested fix
- Primary. In the POSIX startup script (
buildPosixStartupScript), turn on job control before starting the agent, so the agent leads its own foreground process group. The same applies to thestartupSuccessCommandbranch.I tested this in a PTY under an interactive zsh, with macOS- ... then eval "$ORCA_SEQUENCED_STARTUP_COMMAND"; exit "$?"; ... + ... then set -m; eval "$ORCA_SEQUENCED_STARTUP_COMMAND"; exit "$?"; ...
/bin/bash3.2.57 and Homebrew bash 5.3.15. I tried a simple command, the fresh-session guard form, and a function-wrapped command. In every case:- The agent became the foreground group leader. Without the change,
bashwas the leader. - Exit codes were preserved (tested with 7).
- zsh got the terminal back after the agent exited.
- Without a tty,
set -mprinted nothing and the exit code was unchanged.
- The agent became the foreground group leader. Without the change,
- Defense in depth. In
foreground-process-tracker.ts, when the fallback is a POSIX shell and an agent identity is cached, keep returning the cached identity until the scheduled refresh retires it. This is what the Windows branch already does. The existingretireStaleForegroundIdentity()still clears the identity when a scan finds no agent. The cost is that a real exit is detected one refresh later. - Optional. Do not report
hasChildProcesses: falsebased on the tracker's fallback name when the process-table evidence isunverifiable.
Workaround I am using
I set an agent command override (Settings › Agents › OMP › Command). It points to a small wrapper named omp. Before the wrapper execs the real binary, it moves into its own process group and takes the terminal foreground (setpgid + tcsetpgrp). It does nothing if it already leads its group, has no tty, or is in the background.
With the wrapper in place:
- A new worktree started by the setup sequence reads
omp/hasChildProcesses: trueon every 3 s probe. - The false notifications stopped.
The basename must stay omp, because shared/omp-fresh-launch.ts only adds --config "$ORCA_OMP_FRESH_CONFIG" for that executable name.
Note on my local setup
I run a locally modified copy of Orca's OMP status extension, which is the hook reporter. It is not what causes the false exit:
- The daemon readings above come from the process table, not from hooks.
- I ran a controlled A/B in a fresh pane with
ORCA_PANE_KEYunset, which disables the extension, so it posted no hooks. With the plain launch,getForegroundProcessreadbashevery time andinspectProcessreportedhasChildProcesses: false. The wrapped launch readomp/true.
The extension only affects how the notification looks. A mid-turn hook state of working makes Orca drop the snapshot. I expect Orca's own managed extension to report working mid-turn as well, but I have not checked.

Activity
AmethystLiang commentedon Oct 7, 2026
Thanks for reporting. Looking into it