[BUG] CLAUDE_CODE_ATTRIBUTION_HEADER=0 also blocks auto classifier model

Status Fixed / completed
Reported on v2.1.159
Maintainer reply None cached
Activity 5 comments · opened Jun 1, 2026 · closed Aug 19, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When CLAUDE_CODE_ATTRIBUTION_HEADER=0 is set the auto classifier models fails with this message:

 ⎿  Error: claude-opus-4-8[1m] is temporarily unavailable, so auto mode cannot determine the safety of Write right now. Wait briefly and then try this action again. If it keeps failing, continue with other tasks that don't require this
     action and come back to it later. Note: reading files, searching code, and other read-only operations do not require the classifier and can still be used.

(for any model, not just opus)

when unsetting the variable it (auto classifier) works fine reliably

(variable set because of other documenting caching issues with it - i.e. https://github.com/anthropics/claude-code/issues/24168 )

What Should Happen?

Classifier model should not fail, or if this is an intentional anthropic position error message should be clearer.

Error Messages/Logs

Steps to Reproduce

  1. set CLAUDE_CODE_ATTRIBUTION_HEADER=0
  2. fire up claude code
  3. try to write a file to /tmp
  4. get error
  5. exit
  6. unset the var
  7. fire up claude code
  8. try to write a file to /tmp
  9. it works

repro script:

#!/usr/bin/env bash
# Repro: does CLAUDE_CODE_ATTRIBUTION_HEADER=0 break the auto-mode safety classifier?
#
# Method
# ------
# For each model, run `claude -p` in AUTO permission mode and force ONE benign Bash
# tool call that writes a sentinel file. The tool is deliberately NOT allowlisted, so
# the auto-mode safety classifier is the gate that must approve it. Therefore:
#   classifier OK     -> benign action approved -> sentinel file created
#   classifier broken -> "cannot determine the safety" -> blocked -> no sentinel
# Each model is run twice: control (header unset) vs test (header=0). A difference == repro.
#
# Oracle is the sentinel file (robust); the run log is kept for diagnosis and is also
# grepped for the classifier error string to distinguish a true block from any other
# failure.
#
# Models
# ------
# Default sweep: a stable Sonnet baseline, Opus 4.8, and Opus 4.8 with 1M context.
#   * claude-sonnet-4-6     -> clean baseline (widely available; control should pass)
#   * claude-opus-4-8       -> shows the header effect is not 1M-specific
#   * claude-opus-4-8[1m]   -> the variant from the original report
# NOTE: claude-opus-4-8[1m] is ALSO independently affected by transient Opus/1M-context
# capacity outages that emit the SAME error string. If its CONTROL run fails, that's the
# capacity bug, not the header — which is exactly why Sonnet is included as a clean control.
#
# Usage:
#   ./repro-attribution-classifier.sh
#   MODELS="claude-opus-4-8[1m]" ./repro-attribution-classifier.sh     # just the 1M variant
#   MODELS="claude-sonnet-4-6 claude-haiku-4-5" ./repro-...            # custom sweep
set -uo pipefail

CLAUDE="$(command -v claude)"
[ -z "$CLAUDE" ] && { echo "claude not on PATH"; exit 3; }

read -r -a MODELS <<< "${MODELS:-claude-sonnet-4-6 claude-opus-4-8 claude-opus-4-8[1m]}"

LOGDIR="$(mktemp -d /tmp/cc-classifier-logs.XXXXXX)"
TO="$(command -v timeout || command -v gtimeout || true)"
run_to(){ if [ -n "$TO" ]; then "$TO" 120 "$@"; else "$@"; fi; }
slug(){ printf '%s' "$1" | LC_ALL=C tr -c 'A-Za-z0-9' '_'; }

# run_case <model> <label> <env-modifiers...>   (env mod = `-u VAR` or `VAR=val`)
# sets globals: REPLY_CREATED (YES/NO), REPLY_BLOCKED (YES/no)
run_case(){
  local model="$1" label="$2"; shift 2
  local sentinel; sentinel="$(mktemp -u /tmp/cc-classifier-repro.XXXXXX)"
  local prompt="Use the Bash tool to run this exact command and nothing else, then stop: echo OK > ${sentinel}"
  local log="${LOGDIR}/$(slug "$model").${label}.log"
  rm -f "$sentinel"
  run_to env "$@" "$CLAUDE" -p "$prompt" \
      --permission-mode auto --model "$model" --output-format json \
      >"$log" 2>&1
  local ec=$?
  REPLY_CREATED="NO"; [ -f "$sentinel" ] && REPLY_CREATED="YES"
  REPLY_BLOCKED="no"; grep -qi "cannot determine the safety" "$log" && REPLY_BLOCKED="YES"
  printf '    %-8s exit=%-4s sentinel_created=%-4s classifier_blocked=%-4s  log=%s\n' \
         "$label" "$ec" "$REPLY_CREATED" "$REPLY_BLOCKED" "$log"
  rm -f "$sentinel"
}

echo "claude : $CLAUDE ($($CLAUDE --version 2>/dev/null))"
echo "models : ${MODELS[*]}"
echo "logs   : $LOGDIR"
echo

overall=0
for model in "${MODELS[@]}"; do
  echo "=== model: $model ==="
  echo "  [1/2] CONTROL  — CLAUDE_CODE_ATTRIBUTION_HEADER unset"
  run_case "$model" control -u CLAUDE_CODE_ATTRIBUTION_HEADER
  ctrl="$REPLY_CREATED"
  echo "  [2/2] TEST     — CLAUDE_CODE_ATTRIBUTION_HEADER=0"
  run_case "$model" test CLAUDE_CODE_ATTRIBUTION_HEADER=0
  test_created="$REPLY_CREATED"; test_blocked="$REPLY_BLOCKED"
  printf '  verdict: '
  if [ "$ctrl" = "YES" ] && [ "$test_created" = "NO" ]; then
    echo "REPRO CONFIRMED (control works; header=0 blocks; classifier_blocked=$test_blocked)"
  elif [ "$ctrl" = "NO" ]; then
    echo "INCONCLUSIVE — control did not execute (model unavailable / refused). See log."
    overall=1
  else
    echo "NOT REPRODUCED — both runs executed the action."
  fi
  echo
done
echo "all logs: $LOGDIR"
exit "$overall"

Output

bennight@Chriss-MacBook-Air projects % ./repro-attribution-classifier.sh
claude : /Users/bennight/.local/bin/claude (2.1.159 (Claude Code))
models : claude-sonnet-4-6 claude-opus-4-8 claude-opus-4-8[1m]
logs   : /tmp/cc-classifier-logs.c8u1e5

=== model: claude-sonnet-4-6 ===
  [1/2] CONTROL  — CLAUDE_CODE_ATTRIBUTION_HEADER unset
    control  exit=0    sentinel_created=YES  classifier_blocked=no    log=/tmp/cc-classifier-logs.c8u1e5/claude_sonnet_4_6.control.log
  [2/2] TEST     — CLAUDE_CODE_ATTRIBUTION_HEADER=0
    test     exit=0    sentinel_created=NO   classifier_blocked=YES   log=/tmp/cc-classifier-logs.c8u1e5/claude_sonnet_4_6.test.log
  verdict: REPRO CONFIRMED (control works; header=0 blocks; classifier_blocked=YES)

=== model: claude-opus-4-8 ===
  [1/2] CONTROL  — CLAUDE_CODE_ATTRIBUTION_HEADER unset
    control  exit=0    sentinel_created=YES  classifier_blocked=no    log=/tmp/cc-classifier-logs.c8u1e5/claude_opus_4_8.control.log
  [2/2] TEST     — CLAUDE_CODE_ATTRIBUTION_HEADER=0
    test     exit=0    sentinel_created=NO   classifier_blocked=YES   log=/tmp/cc-classifier-logs.c8u1e5/claude_opus_4_8.test.log
  verdict: REPRO CONFIRMED (control works; header=0 blocks; classifier_blocked=YES)

=== model: claude-opus-4-8[1m] ===
  [1/2] CONTROL  — CLAUDE_CODE_ATTRIBUTION_HEADER unset
    control  exit=0    sentinel_created=YES  classifier_blocked=no    log=/tmp/cc-classifier-logs.c8u1e5/claude_opus_4_8_1m_.control.log
  [2/2] TEST     — CLAUDE_CODE_ATTRIBUTION_HEADER=0
    test     exit=0    sentinel_created=NO   classifier_blocked=YES   log=/tmp/cc-classifier-logs.c8u1e5/claude_opus_4_8_1m_.test.log
  verdict: REPRO CONFIRMED (control works; header=0 blocks; classifier_blocked=YES)

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.159

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

_No response_

View original on GitHub ↗

4 Comments

skeptisq · 2 months ago

Having exact same issue. Note CLAUDE_CODE_ATTRIBUTION_HEADER is needed for caching to work on other providers.

aguynamedryan · 2 months ago

Confirming this on Claude Code 2.1.187 (current), Max subscription, direct to api.anthropic.com. Clean A/B — the only variable is CLAUDE_CODE_ATTRIBUTION_HEADER:

| direct, 2.1.187 | header OFF (=0) | header ON (default) |
|---|---|---|
| claude-opus-4-8 | 429 rate_limit_error | 200 |
| claude-sonnet-4-6 | 429 rate_limit_error | 200 |

The main /v1/messages chat is unaffected — only the auto-mode classifier sub-request 429s. And it isn't usage-driven: it reproduces instantly and deterministically on a single gray-zone classification (an ln -sf symlink) and clears the instant the header is re-enabled. Still broken on 2.1.187, so no recent release fixes it.

Repro (~30s):

CLAUDE_CODE_ATTRIBUTION_HEADER=0 CLAUDE_CODE_DEBUG_LOGS_DIR=/tmp/dbg \
  claude --model opus --debug api -p 'Use Bash to run exactly: ln -sf /etc/hostname /tmp/x'
grep classifier_request /tmp/dbg/*.txt   # -> outcome=error errorKind=Error:429
# unset the env var, rerun -> outcome=ok

(I'd originally mis-filed this as an account-side classifier-pool problem in #60438; it isn't — this is the real root cause. Closed mine in favor of this one.)

e9x · 1 month ago

can we get this fixed?

williamrobotma · 1 month ago

Another way to hit this bug, and a much harder one to spot: the background-session daemon keeps the environment of whichever session first started it. Mine was started weeks ago from a shell that had CLAUDE_CODE_ATTRIBUTION_HEADER=0, so every background session since then ran with the variable set, while no shell or config file still had it. Foreground sessions worked; background sessions silently didn't.

Claude Code 2.1.201, Linux, claude.ai Max (OAuth), model claude-opus-4-8.

Effect: for 12+ days, auto mode in every background session denied every Bash call with "claude-opus-4-8 is temporarily unavailable, so auto mode cannot determine the safety of Bash right now...". The same sessions could still reply normally, and foreground sessions running at the same moment got normal approvals. The variable existed only inside the daemon:

$ tr '\0' '\n' < /proc/<daemon-pid>/environ | grep ATTRIBUTION
CLAUDE_CODE_ATTRIBUTION_HEADER=0   # inherited by every background worker

Claude Code's own error dumps (/tmp/claude-$UID/auto-mode-classifier-errors/) show the same error as this issue in 11 background sessions, Jun 23 - Jul 4, all identical:

=== ERROR ===
429 {"type":"error","error":{"type":"rate_limit_error","message":"Error"},"request_id":"req_011Cci5iYa23Z4TTrAbxUX16"}

To confirm the variable (not backgrounding itself) is the cause, I ran the same prompt with claude -p, auto mode, opus, changing only the variable:

| run | result |
|---|---|
| foreground, variable unset (x3) | approved, command ran |
| foreground, variable set to 0 | denied, 429 (req_011CciWRUTenp76xhAsB2NEQ) |
| background via the daemon (x3) | denied, 429, retries forever |

Worth noting on this issue because it is near-impossible to diagnose: the variable is invisible (it only shows up in /proc/<daemon>/environ), the error message hides the 429 ("temporarily unavailable", which the docs describe as server overload, so it reads as an outage; some reports in #74248 / #74351 / #74280 / #69950 may actually be this), and background sessions never recover until the daemon is killed and restarted from a clean shell.

Suggestions: include the HTTP status in the error message, and don't let background sessions inherit a weeks-old daemon environment (or at least surface it in claude doctor).

<sup>*ps: edited: got claude to rewrite this comment and make it less jargony (y) </sup>

Showing cached comments. Read the full discussion on GitHub ↗