[BUG] Stop hook blocks on dirty/untracked working tree and is reprovisioned every Claude Code web session

Status Open
Maintainer reply None cached
Activity 2 comments · opened Aug 13, 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?

In Claude Code web / CCR, the launcher-provisioned Stop hook at:

~/.claude/stop-hook-git-check.sh

exits with code 2 whenever the repository contains modified, staged, or untracked files.

This means a normal development turn that intentionally leaves work uncommitted forces an additional model turn, even when the current user instruction explicitly says not to commit or push.

We verified that changing the hook so dirty/untracked state is non-blocking fixes the problem while preserving the safety check for unpushed commits.

However, the fix does not persist. Every fresh Claude Code web session reprovisions the original blocking hook again, so the behavior returns.

What Should Happen?

Dirty, staged, or untracked working-tree state should not by itself cause the Stop hook to exit 2 or force another Claude turn.

Expected behavior:

  • Dirty/untracked files with no unpushed commits → Stop hook exits 0.
  • Clean working tree with no unpushed commits → Stop hook exits 0.
  • Local commits that have not been pushed to the configured upstream → Stop hook may continue to exit 2 as a safety mechanism.
  • The hook should never automatically commit or push.

Because the hook is reprovisioned in every Claude Code web session, this behavior should be fixed in the Claude Code web / CCR provisioning template rather than requiring users to edit the ephemeral hook in each container.

Error Messages/Logs

Observed Stop-hook behavior:

Modified/staged working tree:
exit 2

Untracked files:
exit 2

Unpushed commits:
exit 2

Verified in-session corrected behavior:

dirty/untracked + 0 unpushed commits → exit 0
clean + 0 unpushed commits → exit 0
1 unpushed commit → exit 2

The corrected shell script passed:

bash -n ~/.claude/stop-hook-git-check.sh

After starting a fresh Claude Code web session, the live hook reverted to the original implementation that exits 2 on dirty/untracked files.

Steps to Reproduce

  1. Start a new Claude Code web session in a Git repository with a configured remote.
  1. Modify an existing tracked file or create a new untracked file.
  1. Do not commit the change.
  1. End the Claude turn.
  1. Observe that the launcher-provisioned Stop hook:

~/.claude/stop-hook-git-check.sh
exits with code 2 because the working tree is dirty or contains untracked files.

  1. Claude is forced into an additional turn even though the work is intentionally uncommitted.
  1. Edit the hook so the dirty/untracked checks no longer exit 2, while leaving the unpushed-commit check unchanged.
  1. Verify:
  • dirty/untracked + zero unpushed commits → exit 0
  • clean + zero unpushed commits → exit 0
  • one unpushed commit → exit 2
  1. Start a fresh Claude Code web session.
  1. Inspect ~/.claude/stop-hook-git-check.sh again.
  1. Observe that the original blocking dirty/untracked logic has been restored.

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

Claude Code web / CCR (version not exposed in web UI)

Platform

Other

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Non-interactive/CI environment

Additional Information

Additional evidence from two separate fresh Claude Code web sessions:

  • ~/.claude/stop-hook-git-check.sh
  • ~/.claude/launcher-settings.json
  • ~/.claude/session-start-git-identity.sh
  • ~/.claude/stop-hook-reply-gate.py

were observed with matching modification timestamps at container/session startup.

No script inside ~/.claude/ was found that rewrites stop-hook-git-check.sh.

The only registered SessionStart hook, session-start-git-identity.sh, does not reference or modify the Stop hook.

This strongly suggests these files are provisioned from an external Claude Code web / CCR container template at session creation.

Project-level .claude/settings.json cannot provide a persistent workaround because hooks are additive and the problematic Stop hook is registered by ~/.claude/launcher-settings.json outside the repository-level configuration.

The in-session fix has been tested twice and works correctly, but it is lost when a fresh Claude Code web session is created.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗