Interactive `claude` sessions are classified as background jobs post-2.1.139, causing bg-only guards to fire on user-foreground work

Status Fixed / completed
Reported on v2.1.143
Maintainer reply ✓ Yes — bogini
Activity 8 comments · opened May 16, 2026 · closed May 27, 2026
💡 Likely answer: A maintainer (bogini, collaborator) responded on this thread — see the highlighted reply below.

Summary

After the agent-view release (v2.1.139, May 11), every Claude Code session — including ones launched interactively by typing claude in a terminal — is set up with $CLAUDE_JOB_DIR, a daemon-managed state file, and a template: "bg" flag. As a result, instructions and guards that were intended for spawned/unattended background jobs now fire on user-foreground sessions too.

The most visible symptom is the worktree-isolation guard: every Edit/Write on a tracked file refuses with a "background session hasn't isolated its changes yet" error and forces the agent to do an EnterWorktree → commit → push → ExitWorktree → pull dance, even for a one-line edit in a session the user is actively typing in.

This is not a misconfiguration on the user's machine — it's the daemon's classification behavior. I reproduce it on a stock install on 2.1.143.

Repro

  1. macOS, fresh terminal. No --bg, no agent-view, no shell wrappers.
  2. cd ~/some/git/repo && claude — open a normal foreground session.
  3. From inside that session, ask the agent to make any single-file edit on a tracked file.
  4. Observe the Edit tool fail with:

> This background session hasn't isolated its changes yet. Call EnterWorktree first so edits land in a worktree instead of the shared checkout, then retry this edit using the worktree path. (To disable this guard for this repo, set "worktree": {"bgIsolation": "none"} in .claude/settings.json.)

  1. The agent's system prompt also includes the line "This session runs as a background job. The user may be chatting with you live or may have stepped away to check results later — respond naturally either way, and don't refer to yourself as 'a background agent.'" — which itself acknowledges the awkwardness.

Evidence the daemon is the cause

Process tree

A single claude invocation spawns a daemon + spare-agent pool:

44013  claude                                                            # foreground (terminal s000)
44581  claude daemon run --origin transient --spawned-by {...,"pid":44013}
44603  --bg-pty-host  .../fb780c90.pty.sock 200 50 --  --bg-spare ...
44605  --bg-spare     /tmp/cc-daemon-501/.../fb780c90.claim.sock
44608  --bg-pty-host  .../90e4af4c.sock    162 36 --  --session-id 90e4af4c-... --agent claude
44611  --bg-pty-host  .../30bad891.pty.sock 200 50 --  --bg-spare ...
44615  --session-id   90e4af4c-2ad8-4c0e-906e-7edbc3a3ad1e --agent claude
44616  --bg-spare     /tmp/cc-daemon-501/.../30bad891.claim.sock

So:

  • The foreground claude (44013) auto-spawns a daemon (44581).
  • The daemon keeps a warm spare agent (44615) and bg-pty hosts ready, presumably so (agent view) feels instant.
  • Everything — including the user's interactive session — gets registered as an "agent" with a job dir.

Job-dir contents for the foreground session

$ env | grep CLAUDE_JOB_DIR
CLAUDE_JOB_DIR=/Users/smabe/.claude/jobs/ec39b0b1

$ cat $CLAUDE_JOB_DIR/state.json | head -20
{
  "state": "blocked",
  "tempo": "active",
  ...
  "template": "bg",
  "respawnFlags": ["--effort", "high", "--permission-mode", "auto"],
  "name": "push-code-changes",
  "nameSource": "auto",
  ...
  "cliVersion": "2.1.143",
  "cwd": "/Users/smabe/projects/HealthData",
  ...
}

template: "bg" is set on the user-foreground session. The daemon also auto-named the session ("push-code-changes") from the agent's last bash output, the way agent-view names spawned background jobs.

Why this is a bug, not a feature

Several harness-level behaviors were written for the old model where $CLAUDE_JOB_DIR implied "unattended bg work that may collide with the user's working copy and other parallel jobs":

  1. Worktree-isolation guard — refuses Edit/Write on tracked files until EnterWorktree. Sensible for an unattended job; pure friction for a user typing into the same session.
  2. System-prompt "Background Session" block — tells the agent the user "may have stepped away." Wrong for the user-foreground case (and the prompt now has to apologize for itself with "respond naturally either way").
  3. Auto-naming — naming the session from agent output ("push-code-changes") is great for unattended jobs the user finds later in agent-view. Less useful, mildly confusing, for an interactive terminal session.

Discrimination signal exists but isn't being used. The daemon already has:

  • --origin transient vs other origins
  • --spawned-by {label, cwd, pid} for spawned jobs
  • --bg / --bg-spare for actual bg pool entries vs the user's TTY-attached session
  • A TTY-attached child process visible to the daemon

Any of those could classify "the user is talking to this session live" vs "this session was spawned to run unattended."

Suggested fix

Distinguish the two cases at the daemon layer and propagate to:

  1. Set template: "foreground" (or omit template) on user-typed sessions; reserve template: "bg" for sessions spawned via claude --bg, agent-view "new background session," Agent tool subagents, Task API spawns, etc.
  2. The worktree-isolation guard should fire only on template: "bg", not on the presence of $CLAUDE_JOB_DIR.
  3. The "Background Session" block in the system prompt should only be inserted for template: "bg".
  4. Auto-naming from agent output should only happen for template: "bg".

The bgIsolation: "none" setting documented in the guard message is a workaround that disables protection wholesale, which is the wrong knob — users running real parallel bg work in the same repo want the protection on; they just don't want it firing on their interactive session.

Environment

  • claude --version2.1.143 (Claude Code)
  • macOS 25.5.0 (darwin), zsh, Ghostty terminal
  • Plain claude invocation in a project directory, no flags, no shell wrappers

Related

  • #59702 — EnterWorktree/ExitWorktree cwd-pinning bug (filed today by another user). Compounds with this one: the forced worktree dance is the trigger surface for #59702, so fixing this issue would reduce exposure to that one too.
  • #59846 — Naming feedback: "agent view" conflates with the existing "agent" concept. Separate concern, same underlying conflation: "agent" and "session" are no longer cleanly distinguished post-2.1.139, and harness behaviors keyed off "is this a session?" assumptions break in surprising places.

View original on GitHub ↗

8 Comments

kcarriedo · 3 months ago

This is a clear write-up of an architecturally interesting bug, and the root cause you identify — "$CLAUDE_JOB_DIR is being used as a proxy for template == 'bg'" — is worth naming as its own concern, because the same conflation is going to keep producing surprises across other subsystems that branch on session classification.

A few observations from running supervisor-shaped processes that orchestrate Claude Code daemons (separate top-level process, owns lifecycle of a pool of claude invocations, distinguishes user-interactive vs. unattended children):

The "is this background" question has at least four orthogonal axes, and v2.1.139 collapsed them into one signal ($CLAUDE_JOB_DIR != ""). Concretely:

| Axis | Question | Honest signal |
|---|---|---|
| TTY-attachment | Is a human typing at this process right now? | isatty(stdin) of the agent process |
| Spawn origin | Did a user invoke this, or did another agent/the daemon spawn it? | --spawned-by / --origin transient vs --bg |
| Persistence | Will this process outlive the user's terminal session? | --bg flag / process-group detachment |
| Worktree isolation policy | Are concurrent edits expected against the same checkout? | repo-level config / explicit policy |

The worktree-isolation guard is keyed off axis 4, but the daemon is computing axis 4 from axis 3 via $CLAUDE_JOB_DIR, which is itself a proxy for whether the daemon has registered the session (which now happens on every invocation since 2.1.139). All four axes used to align by accident — pre-2.1.139, only spawned bg jobs got a job dir, so the four-way agreement was implicit. The agent-view release broke that alignment by making the warm-spare pool real, and the system-prompt apology ("respond naturally either way") is the artifact of that decoupling not being fully threaded through downstream consumers.

The proposed fix (template: "foreground" vs template: "bg") is correct but is one of two complementary moves. The other is: every consumer of "is this background?" should branch on the specific axis it actually cares about, not on template. The system-prompt block is genuinely about TTY-attachment (axis 1) — "the user may have stepped away." Auto-naming is about persistence (axis 3) — agent-view needs a label to surface in a list later. The worktree-isolation guard is about edit-collision policy (axis 4). Routing all three through template works, but the next subsystem that wants to branch on "is the user typing" will reach for $CLAUDE_JOB_DIR again if template is the only public signal, and you'll get the same conflation in a different surface six months from now. Exposing the four signals separately (or at minimum: tty_attached, spawned_by_user, persistent, and letting template be a derived convenience) is more defensible.

On the bgIsolation: "none" workaround being the wrong knob: agree completely, and the reason matters. The user wants the guard for actual bg work in the same repo — operators running real fleets (1 user + N supervised agents in distinct worktrees) need that collision protection. The workaround disables protection wholesale; the right fix only disables it on TTY-attached sessions. This is identical in shape to the permissions: session-scoped issue raised in #54898 — once permissions/guards/templating are session-scoped but the underlying axes are agent-scoped, every per-agent policy decision has to be hand-routed.

One small empirical addition you might be able to verify quickly: is the auto-name ("push-code-changes") being set from agent output after the agent's first bash call, or pre-emptively from cwd at spawn time? On the supervised-process side, it appears post-first-bash — which means the auto-naming pipeline is consuming bash output on every session including foreground ones, presumably to feed the agent-view sidebar. If so, the relevant fix isn't just suppressing the write of the name on foreground sessions, but suppressing the read of bash output by the daemon's naming subsystem when the session is TTY-attached. That's a different code path than the four behaviors you list.

The TTY check (axis 1) is the cheapest, most legible signal of "user is actively here" — and it's the one the OS already gives you for free, untouched by daemon registration timing.

smabe · 3 months ago

Hey thanks for the reply. I noticed that the auto name happened after the first bash call

Also running in to issues working in the main chat session

<img width="1628" height="669" alt="Image" src="https://github.com/user-attachments/assets/19ac064b-df59-49cd-bcdc-c531ea27d7ad" />

smabe · 3 months ago

Adding a fresh repro + two new subcases from a 4-hour autonomous-planning session today (v2.1.143, macOS, stock install). Context: I was working in a bg job orchestrated by clu, my personal plan-orchestrator. Bg job, but the operator was actively chatting with me throughout — different from the issue body's foreground case, same friction.

Hit bgIsolation 5 times for what should have been single-shot edits:

  1. Writing 3 plan files to plans/ — EnterWorktree → write → commit → ExitWorktree → ff-merge → push → cleanup. ×3.
  2. Writing one SKILL.md fix. Same dance.
  3. Fixing a YAML frontmatter bug in another SKILL.md. Same dance.

Each round-trip cost ~5 min wall time, one prompt-cache miss, and forced operator-visible merge commits for what could have been linear edits. Net session cost: ~15 min friction + one entire 5-min cache window invalidated mid-flow.

New subcase the issue doesn't enumerate: bg session where the operator is actively present. The system prompt's "respond naturally either way" line covers this rhetorically but the guard treats all bg sessions as if the operator is away. Per @kcarriedo's four-axis breakdown — "is the operator present in this conversation right now" is arguably a fifth axis (or a refinement of TTY-attachment for the bg case). Today, even when the operator types into a bg job's chat continuously, the guard fires.

New failure mode: edits blocked mid-merge. During a git merge --no-ff <branch> that produced conflicts in two files, I couldn't use Edit to resolve the conflict hunks (guard fired). EnterWorktree mid-merge isn't an option — the merge state is on main, not transferable. I escaped by writing a python heredoc that rewrote each conflict block via Path.read_text().splitlines() + slicing. Worked, but the natural tool path was Edit and the workaround added ~5 min with zero safety upside (the merge was already creating commits on main; the guard's "you might clobber the user's checkout" premise is moot once git merge is in flight).

A carve-out for state == MERGE would close this specific cliff cheaply. Longer-term fix is per @kcarriedo's analysis — route each guard's decision through the axis it actually cares about. For this specific guard (axis 4 = edit-collision policy), "is there an active merge in progress?" is a hard "no other agent should be touching this checkout anyway" signal that overrides the worktree-isolation policy without needing the full axis cleanup.

marelons1337 · 3 months ago

Can confirm that adding below line to .claude/settings.json eliminates the problem.

  "worktree": {
    "bgIsolation": "none"
  }
emiliomartucci · 3 months ago

Hit by this on a Linux ARM64 server (Ubuntu 24.04 aarch64, Hetzner cax41, 16 cores / 32 GB RAM), Claude Code v2.1.145.

Repro / impact:

The orphan --bg-spare and --bg-pty-host daemons survive the parent claude session exit and accumulate on long-lived hosts. After ~30 hours of normal use (multiple claude --resume/--dangerously-skip-permissions sessions in tmux), I had two daemon processes hammering the disk continuously:

PID 819514  claude .../versions/2.1.145 --bg-spare /tmp/cc-daemon-1000/.../*.claim.sock
            → 712 MB/s sustained read (sdb root fs)

PID 819504  claude .../versions/2.1.145 --bg-pty-host /tmp/cc-daemon-1000/.../*.pty.sock 200 50 --bg-spare ...
            → 560 MB/s sustained read

Combined ~1.27 GB/s sustained disk read from the two orphans alone. With other Claude sessions still running (--resume <uuid> workers spawned by an autonomous orchestrator session), aggregate hit ~2 GB/s and load average climbed to 105 on a 16-core box. The Hetzner Cloud metrics graph shows the sustained 1500% CPU + 2 GBps read pattern for hours.

Process tree confirms the parent claude TUI was long gone but the daemons kept running with --origin transient style cmdlines.

What broke as a side effect:

  • I burned through ~30 % of my weekly Claude usage limit in roughly 30 hours on this single misbehaving host (cumulative ~28.7 M output tokens across the orchestrator + worker pool, while the sessions were mostly idle waiting on user input — the daemons were just churning the disk and triggering re-indexing).
  • Workspace-wide ugrep workers (spawned by Claude for file indexing) piled up to ~266 MB/s read each, presumably waiting on the same daemons.
  • sshd was eventually OOM-killed in a downstream cascade (separate but related: the daemons keep RSS modest but pin the page cache, which forces the kernel to evict everything else under pressure).

Workaround that worked:

pgrep -af "bg-spare|bg-pty-host"     # identify orphans
kill -9 <pids>                        # safe per the discussion in this thread

Load went from 105 → 10 in ~30 seconds, disk read collapsed to baseline immediately. No data loss, all open claude --resume sessions in tmux were unaffected (the orphan daemons had no parent TTY left).

Asks:

  1. On daemon startup, register a cleanup hook that exits when the spawning TTY is gone (or when the $CLAUDE_JOB_DIR is orphaned for >N seconds).
  2. The disk read pattern looks like the daemon is doing repeated full scans of ~/.claude/projects/<encoded-cwd>/ and/or workspace ugrep indexing on every event — could that loop be bounded / cached?
  3. For users on Linux servers with multiple tmux-resident sessions, the v2.1.139 "every session is bg" change makes the daemon pool grow unbounded across reboots/sessions. A --no-daemon / CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 that is actually respected for interactive sessions would unblock this.

Downgrading to 2.1.104 (the last version before the agent-view bg-pool change) is my current plan.

ellekdev · 3 months ago

MacOS, Cross-session filesystem corruption from EnterWorktree/ExitWorktree, 2.1.150

Adding a huge secondary issue that I am also facing related to this. When one session calls EnterWorktree or ExitWorktree, it corrupts filesystem permissions for ALL other concurrent sessions across the entire repo — not just the session that invoked it. I don't know what causes this, but it happened twice back to back after upgrading to 2.1.1.50

Repro:

  1. Have multiple claude sessions running (foreground, background, agents in worktrees)
  2. One session gets forced into EnterWorktree by the bg isolation guard
  3. That session calls ExitWorktree to return to main
  4. All other running sessions immediately start getting EPERM: operation not permitted on Read, Write, and Edit — even on files in completely separate worktrees

Impact:

  • Every concurrent agent is dead — can't read, can't write, can't edit
  • The EPERM persists even after killing and restarting sessions
  • Files on MAIN (not in any worktree) lose permissions
  • The only recovery is killing all claude processes and restarting

Cannot be blocked:

  • EnterWorktree and ExitWorktree are not in the deny-list schema — the regex pattern for permissions.deny doesn't include them as valid tool names
  • There is no hook matcher for these tools (only Agent, Bash, Edit, Read, Write)
  • bgIsolation: "none" is supposed to prevent the guard from firing, but the guard fires anyway (the core issue in this thread)
  • So: the guard forces a worktree entry that can't be blocked, which then corrupts all other sessions, with no recovery path except killing everything

This is a data-safety issue. The worktree lifecycle is modifying filesystem permissions on the main checkout and other worktrees that it has no business touching. This is incredibly annoying and dangerous. This is causing file permission corruption.

I don't know if this is also an issue with some new claude daemon and updating. I updated, but I'm not sure how to restart the DAEMON itself?

bogini collaborator · 3 months ago

Fixed — interactive sessions are no longer misclassified as background jobs. Please reopen if you still see bg-only guards firing on foreground work in a current version.

github-actions[bot] · 1 month ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.