Pinned background jobs auto-respawn from done state, firing agent-start notifications at idle
Summary
Pinned background jobs in ~/.claude/jobs/pins.json whose state.json shows state: done keep getting re-claimed by the daemon's "fleet" mechanism. Each reclaim spawns a transient claude --resume <id> --agent claude process, which fires the agent-start notification hook every time. With several pinned-and-forgotten jobs, this produces a steady stream of notification chimes at idle, with no active sessions.
We discovered this because we use peon-ping for agent-start audio cues — repeated pings with no Claude sessions running made the regression obvious. Without a notification system you'd never notice this; it would just be invisible CPU/process churn.
This is a regression or a side-effect of recent changes to pinned-job / fleet behavior. Pinned jobs never used to auto-respawn after completion.
Environment
- Claude Code CLI: 2.1.152 (also seen with 2.1.150 and 2.1.146 — pinned jobs span multiple recent versions)
- Platform: macOS (Darwin 25.5.0)
- Date observed: 2026-05-27
Reproduction
- Create one or more pinned background jobs (any flow that produces a job and pins it).
- Let those jobs finish — i.e.
state.jsonreachesstate: done,tempo: idle. - Idle the machine; do not open any new Claude session.
- Observe: at irregular intervals (every few minutes) the daemon's fleet refresh re-claims one of the done jobs. A short-lived
claude --resume <session-id> --agent claudeprocess spawns and exits. The agent-start hook fires.
If you have an audio notification system wired to agent-start events, you'll hear it. Otherwise, watch ~/.claude/daemon.log for bg claimed-spare <id> (fleet) lines.
Evidence
~/.claude/daemon.log (timestamps from 2026-05-27 16:24–16:25 UTC, machine completely idle from a user perspective):
[2026-05-27T16:24:21.869Z] [bg] bg claimed-spare d8aa131c (fleet)
[2026-05-27T16:24:21.870Z] [bg] bg spare spawned host pid=90415
[2026-05-27T16:24:50.544Z] [bg] bg claimed-spare caa46f6c (fleet)
[2026-05-27T16:24:50.545Z] [bg] bg spare spawned host pid=99253
[2026-05-27T16:25:02.986Z] [bg] bg claimed-spare 7ea7434e (fleet)
[2026-05-27T16:25:02.987Z] [bg] bg spare spawned host pid=3951
[2026-05-27T16:30:10.102Z] [bg] bg claimed-spare dc141d80 (fleet)
[2026-05-27T16:30:10.106Z] [bg] bg spare spawned host pid=12478
Two example jobs caught during the spawn window:
7ea7434e—state: done,tempo: idle, originally created 2026-05-26, last updatedAt 2026-05-27T16:25:02.987Z (i.e. updated by the fleet reclaim itself).respawnFlags: ["--agent", "claude"].caa46f6c—state: done,tempo: idle, originally created 2026-05-21, last updatedAt 2026-05-27T16:24:50.545Z.respawnFlags: ["--add-dir", "/Users/.../ghost", "--agent", "claude"].
ps auxww during the spawn window shows the transient resume processes, e.g.:
0xnfrith 23824 ... claude --resume caa46f6c-... --add-dir ... --agent claude
0xnfrith 23825 ... claude --resume 7ea7434e-... --agent claude
These exit within seconds, which is why they're hard to catch on a single ps sweep.
At the time of report, ~/.claude/jobs/pins.json contained 14 entries — the majority were old done jobs that had been incidentally pinned over the last week.
Workaround (confirmed working)
Remove the IDs of any done-state jobs from ~/.claude/jobs/pins.json. The notifications stop immediately because the daemon no longer re-fleet-claims them.
Suggested fix
Pick one (or both):
- Daemon side (preferred): the fleet reclaim loop should skip pinned jobs whose
stateisdone. Pinning a job should mean "keep it handy if I want to resume it," not "keep respawning it forever." If there's a real reason to periodically wake done jobs (e.g. transcript link-scanning — there arelinkScanOffset/linkScanPathfields instate.jsonthat hint at this), it should be a silent operation that doesn't trigger session-activation hooks. - Hook side: the agent-start notification hook should fire only when a session is opened by a real interactive client, not when the daemon respawns a
donejob for fleet-warming purposes. Pass a flag through to the hook (e.g.CLAUDE_HOOK_TRIGGER=fleet-reclaimvsinteractive) so external notification systems can filter.
The daemon-side fix is the stronger one — it addresses CPU/process churn at the source. The hook-side fix would silence the symptom but leave the wasted respawn cycles.
Why this matters
- For users with notification systems, the audio is annoying and undermines the signal value of agent-start cues.
- For users without notification systems, this is silent CPU/process churn — periodic claude process spawns that they never asked for.
- The job list is the surface where pin behavior is most user-visible; if pinning quietly produces respawn churn, users will avoid pinning, which defeats the point of the feature.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗