BubbleCodeFounding access

Why Claude Code stalls waiting for permission

Updated July 27, 2026 · 6 min read

With permission checks on, agent throughput is bounded by how fast a human answers, not by the model. Most people try to fix that by removing the check — but the number that actually matters is the gap between an agent blocking and you noticing, and that's a delivery problem with much cheaper solutions.

You start an agent on a refactor, switch to something else, come back twenty minutes later, and find it has been sitting at *"Do you want to run npm run build?"* for nineteen of them. Nothing is broken. The agent is doing exactly what it should. You just weren't there.

This is the defining friction of running Claude Code unattended, and it's worth being precise about it, because the obvious fix is the wrong one.

Why an agent blocks at all

In any permission mode short of bypassPermissions or dontAsk, certain tool calls stop and wait for a human. That's the feature. An agent about to run a shell command it inferred from a README, on a machine holding your SSH keys, *should* check first.

What blocks, in rough order of frequency:

  • Shell commands — even in acceptEdits, which auto-approves file edits, Bash still asks.
  • Edits outside the working directory, or reads of paths a deny rule covers.
  • Anything matching an explicit ask rule you wrote precisely so it would stop.
  • Network fetches to URLs that didn't come from you, which have their own provenance check.

None of that is malfunction. The agent is blocked because you asked it to be. Permission modes explained covers exactly what each mode does and doesn't stop for.

The arithmetic

Here's the part worth sitting with. Take a two-hour task that blocks eight times — a fairly typical refactor with a test suite.

How fast you noticeDead time per taskTwo-hour task becomes
~5 seconds (you're watching)under a minute2h 01m
~2 minutes (terminal on a second screen)16 minutes2h 16m
~10 minutes (you're in another app)80 minutes3h 20m
~25 minutes (you're in fullscreen, or away)3h 20m5h 20m

The model didn't get slower. Your *notification latency* got worse, and it multiplies by the number of blocks. Now run three agents: the dead time triples, and the interruptions arrive from three directions at unpredictable moments, so each one also costs you the context you were holding for your own work.

The number to optimise

It isn't tokens per second and it isn't the number of prompts. It's the gap between *an agent blocks* and *you answer*. Everything below is a way of shrinking that gap, and they differ mainly in what they give up to do it.

Fix 1: block less often

The cheapest win, and the one people skip. Most blocks are the same five commands. Approve them once as a rule rather than thirty times as a decision:

json
{
  "permissions": {
    "defaultMode": "acceptEdits",
    "allow": [
      "Bash(npm run test *)",
      "Bash(npm run build)",
      "Bash(npm run lint *)",
      "Bash(git status)",
      "Bash(git diff *)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(git push *)",
      "Read(./.env)"
    ]
  }
}

Gives up: nothing meaningful, as long as the allowlist stays boring and reversible. deny takes precedence over allow, so the floor holds. This alone often halves the number of stops.

Fix 2: hear about it faster

A hook on Notification or PermissionRequest fires the moment an agent wants you, and the payload includes cwd — so the notification can name the project rather than just saying something happened:

bash
#!/usr/bin/env bash
# ~/.claude/hooks/notify.sh
set -euo pipefail

project=$(cat | /usr/bin/python3 -c \
  'import json,sys,os; print(os.path.basename(json.load(sys.stdin).get("cwd","")))')

/usr/bin/osascript -e "display notification \"Waiting on you\" \
  with title \"Claude Code — $project\" sound name \"Ping\""

Gives up: reliability. macOS suppresses banners over fullscreen apps and under Do Not Disturb, and a banner that arrives while you're reading something else is gone in seconds with no way to get it back. Good enough at your desk, unreliable exactly when you most need it. Full walkthrough of the options.

Fix 3: remove the check

--dangerously-skip-permissions, or defaultMode: "bypassPermissions". Latency goes to zero because nothing ever asks.

Gives up: the safety property, entirely. The agent still runs as you, with your keys and your network — bypassing doesn't sandbox anything, it just stops asking. That's a defensible trade in a disposable container and a bad one on a laptop. The full risk model is worth reading before making this permanent, because it's the change people make once in frustration and never revisit.

There's also dontAsk, which suppresses prompts and *skips* the blocked action instead of escalating. No stall, but you come back to a task that reports success with pieces quietly missing — often worse than either alternative.

Fix 4: make being asked cheap

The remaining option is to keep the check and attack the delivery: a persistent on-screen surface that isn't a Notification Center banner, so it isn't subject to banner suppression, and that lets you answer in place rather than hunting for the right terminal.

On macOS this means a floating panel at a high window level with .fullScreenAuxiliary set (so it can join a fullscreen Space) and .nonactivatingPanel (so clicking it doesn't yank you out of what you're doing). That combination is why an overlay can sit over a fullscreen video, take a click, and leave the video playing.

That's what BubbleCode is: a bubble that reflects the state of every running chat, pulses when one needs an approval, and expands in place so you can answer without opening a window or finding a tab.

Gives up: screen real estate, and a small amount of trust in another process sitting near your permission flow — which is a fair thing to ask about. The answers worth wanting are specific: it launches your own installed claude CLI with your existing login, leaves the CLI in its normal permission mode, and relays your explicit allow or deny rather than deciding anything itself.

In practice

  1. 01Allowlist the five commands you keep approving. Measure again — this usually removes half the stops.
  2. 02Keep deny rules on anything irreversible, so a shrinking prompt count never shrinks the floor.
  3. 03Add a notification hook. It's free, it takes ten minutes, and it's a real improvement over polling with your eyes.
  4. 04If you're regularly in fullscreen or away from the terminal, that's where the hook stops being enough — and it's the specific gap worth spending money to close.
  5. 05Reach for bypassPermissions only where the environment is disposable. Frustration is a bad reason to make it your default.

FAQ

Why is Claude Code stuck and not doing anything?

Most often it's waiting on a permission decision. In any mode except bypassPermissions or dontAsk, shell commands and out-of-scope edits stop until a human approves them — the session looks idle because it is, pending your answer.

How do I stop Claude Code asking for permission constantly?

Add specific rules to permissions.allow for the commands you approve reflexively, rather than switching off checking entirely. A handful of allow rules typically removes most prompts while keeping the check on irreversible operations.

Does a blocked Claude Code agent time out?

A permission prompt waits for you rather than expiring on a short timer, so an unattended agent can sit blocked indefinitely. That's why the gap between blocking and noticing matters more than the decision itself.

What is the difference between dontAsk and bypassPermissions?

bypassPermissions runs everything without asking. dontAsk suppresses the prompt but skips the action instead of running it — so the session keeps moving, but silently does less than you asked for.

Keep reading