Pinned background jobs auto-respawn from done state, firing agent-start notifications at idle

Resolved 💬 1 comment Opened May 27, 2026 by 0xnfrith Closed May 28, 2026

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

  1. Create one or more pinned background jobs (any flow that produces a job and pins it).
  2. Let those jobs finish — i.e. state.json reaches state: done, tempo: idle.
  3. Idle the machine; do not open any new Claude session.
  4. 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 claude process 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:

  • 7ea7434estate: 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"].
  • caa46f6cstate: 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):

  1. Daemon side (preferred): the fleet reclaim loop should skip pinned jobs whose state is done. 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 are linkScanOffset / linkScanPath fields in state.json that hint at this), it should be a silent operation that doesn't trigger session-activation hooks.
  2. 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 done job for fleet-warming purposes. Pass a flag through to the hook (e.g. CLAUDE_HOOK_TRIGGER=fleet-reclaim vs interactive) 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗