Quick answer

When Claude Code appears stuck, do not immediately kill it or repeat the same prompt. It may be waiting for your permission, running a command that genuinely takes time, compacting a crowded context, or waiting for a usage-limit reset. A process is much more likely to be dead when it has exited, its terminal job is gone, or a watcher has observed no log or state change past a timeout you chose for that task.

Start by looking for an approval prompt and checking the process. For unattended work, define the safe autonomy boundary before starting, record a completion marker and exit code, and watch a log rather than guessing from a quiet terminal.

Why an agent can look stuck when it is not

It is waiting for approval

Claude Code normally asks before actions such as shell commands, writes, or network access, depending on the active permission mode and rules. The agent cannot make the next move until someone answers that prompt. This is an intentional safety pause, not a model hang. Scroll to the latest terminal output and look for a permission request, then use /permissions to inspect the applicable allow, ask, and deny rules.

Do not solve prompt fatigue by broadly bypassing protections in a valuable working tree. Instead, pre-approve a narrow, understood set of commands and paths, or use an isolated disposable environment for a task that truly needs wider autonomy. A denial is useful evidence: it tells you that the run’s declared boundary and requested action disagree.

A tool call is still running

Tests, builds, package installs, network requests, database migrations, and searches can be quiet for a long time. The model may be waiting on the tool rather than “thinking.” Open another terminal and inspect the child process, its CPU time, and recent output. On macOS or Linux, replace the PID below with the process ID shown by your shell or watcher:

ps -p PID -o pid=,ppid=,stat=,etime=,time=,command=
pgrep -af 'claude|node|npm|pytest|go test'

# Follow a known log without interrupting the job
tail -n 40 -f .agent-runs/feature-a.log

A process in a running state with increasing CPU time or a log that is still changing is working, even if the Claude interface has no prose response yet. Conversely, a network tool can be alive but blocked on a remote service. Give every external operation a timeout; otherwise an unavailable host can look exactly like a stalled agent.

Context has become too large or noisy

Claude Code’s context contains conversation history, file contents, command output, instructions, loaded skills, and other session material. Large logs and repeated full-file reads consume space and attention. Automatic compaction can summarize history, but it is not a substitute for a clean task boundary. Use /context to see where the window is going. Use /compact with a concrete focus when the current task must continue, or /clear when switching to unrelated work.

Before clearing, write a short handoff in the repository or issue: objective, files changed, command results, unresolved error, and next command. That preserves the facts a new session needs without forcing it to reconstruct them from a huge transcript. Do not keep appending “try again” messages; they add context without adding evidence.

A rate or usage limit has paused requests

A plan or account limit is different from a frozen local process. Claude Code reports a reset time when a session, weekly, or model-specific allowance is exhausted. Run /usage and read the displayed message. Waiting until the stated reset, changing to an allowed model if your account supports it, or purchasing/administering extra usage are account decisions—not fixes that a shell restart can provide.

Classify the quiet terminal before intervening
EvidenceStateBest next action
Visible permission questionAwaiting approvalApprove only the understood action or adjust a narrow rule
PID exists; log or CPU time changesWorkingWait to the task timeout and continue watching
Usage message includes a reset timeRate or plan limitedWait or make an account-level choice
Exit code or terminal job is presentFinished or failedRead the final output; do not call it a hang
PID is gone; no DONE or FAILED markerUnobserved interruptionInspect the log, then resume from a small verified checkpoint

Make the next run autonomous in the right places

The useful fix is an autonomy clause: a short instruction that names the outcome, the safe operating boundary, evidence to leave behind, and the stop conditions. It gives an agent room to finish routine work while retaining human control over irreversible actions. It is not a request to ignore safety or to silently deploy.

Goal: implement and verify the parser change.

You may read and edit files under src/ and tests/; run the named test suite;
and create files under .agent-runs/. Do not push, deploy, alter credentials,
change dependencies, or touch files outside those paths. If blocked, stop and
write the blocker plus the next safe command to .agent-runs/feature-a.status.
Finish by writing DONE with the exact test command and exit code.

Keep the clause specific to the job. “Work autonomously” without boundaries forces either repeated approval prompts or unsafe assumptions. Define the writable paths, allowed commands, maximum elapsed time, prohibited side effects, and the evidence that counts as complete. If a command could delete data, publish code, access secrets, or create a bill, leave it out of the clause and require an explicit decision.

Set timeouts at the tool boundary

Use the operating system to put an upper bound on commands that can wait forever. GNU/Linux has timeout; macOS has no built-in command with the same interface, so use a CI timeout, a process supervisor, or a small watcher. A timeout should create a diagnostic result, not pretend the work succeeded.

# GNU/Linux: return a nonzero status if the command exceeds 20 minutes
timeout --signal=TERM 20m npm test
status=$?
printf 'exit=%s\n' "$status"

# In a POSIX shell, preserve a pipeline's test status deliberately
set -o pipefail
npm test 2>&1 | tee .agent-runs/test.log

When a timeout fires, save the tail of the log, the process tree, and the last completed checkpoint. Then decide whether the bottleneck is a test deadlock, a slow dependency download, a remote outage, or a badly scoped task. Restarting without that distinction repeats the same uncertainty.

Run long work in the background with a watcher

A background process needs an observable contract. Capture its PID, stream its output to a per-run log, and write a terminal marker containing the actual exit code. The marker is stronger evidence than a terminal that happens to be quiet. This simple shell pattern works for any command you are authorized to run; substitute your own Claude Code invocation and prompt mechanism.

mkdir -p .agent-runs
run_id="feature-a-$(date +%Y%m%d-%H%M%S)"
log=".agent-runs/$run_id.log"
status=".agent-runs/$run_id.status"

(
  claude --print 'Implement the scoped task and report verification.' >"$log" 2>&1
  exit_code=$?
  if [ "$exit_code" -eq 0 ]; then
    printf 'DONE exit=%s\n' "$exit_code" >"$status"
  else
    printf 'FAILED exit=%s\n' "$exit_code" >"$status"
  fi
) &
pid=$!
printf '%s\n' "$pid" > ".agent-runs/$run_id.pid"
printf 'run=%s pid=%s log=%s\n' "$run_id" "$pid" "$log"

The wrapper’s exit code indicates whether that invocation returned successfully; it does not prove that its code change is correct. Pair the marker with the exact test command, a diff review, or another acceptance check. Also note that claude --print is suitable only when it matches your installed CLI workflow; check claude --help rather than copying flags into a production script blindly.

pid=$(cat .agent-runs/feature-a-YYYYMMDD-HHMMSS.pid)
status=.agent-runs/feature-a-YYYYMMDD-HHMMSS.status

if [ -f "$status" ]; then
  cat "$status"
elif kill -0 "$pid" 2>/dev/null; then
  echo "RUNNING: PID $pid still exists"
else
  echo "UNKNOWN: process exited without a terminal marker"
fi

Add a freshness check for the log when the work has a known maximum quiet period. For example, record the modification time at each poll and alert after 15 minutes with no change—but make the interval longer than normal compilation or test silence. A watcher should report uncertainty, not kill a legitimate slow job by default.

When to restart Claude Code—and when not to

Restart or open a fresh session when the process has exited, an error is unrecoverable, context is repeatedly thrashing, or the next task is genuinely unrelated. Before doing so, preserve the current diff and the smallest handoff note. A new session is often cleaner than pushing a bloated conversation through more compaction, but it cannot recover an unsaved terminal state.

Do not restart merely because a tool has been quiet for a few minutes. First check approval, PID, log freshness, network/service health, and usage state. If you interrupt an active job, use the normal terminal interrupt, wait for it to stop, then verify whether it wrote a marker. Never start a duplicate run against the same migration, deployment, or shared branch until you know the first one is gone.

The practical rule is simple: a terminal is not a status system. Treat permission prompts, process state, exit codes, logs, and explicit DONE or FAILED markers as separate signals. That turns “Claude Code is hanging” from an anxious guess into a recoverable operational diagnosis.

Primary sources