[BUG] Claude code race condition got into unrecoverable state

Resolved 💬 3 comments Opened Oct 13, 2025 by mattgodbolt Closed Jan 9, 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?

I typed a message just as claude finished a task. It then ignored that message but appeared to be running todos (ignoring my message), even though I could enter new messages still. I up arrowed and hit enter to reissue my message and then it got stuck flashing around (see screencast).

I can't interrupt with ^c, ^z or escape and will have to kill the process.

Claude is taking 123% CPU too:

 972070 matthew   20   0   71.5g 811392  45364 R 123.8   5.2   9:09.74 claude                                                 

kill didn't kill it, I had to kill -9 it

https://github.com/user-attachments/assets/2f825a8d-129d-429a-a8a8-93329eb67a52

What Should Happen?

Should not get stuck.

Error Messages/Logs

> rglobbing the _entirety_ of a clojure installation sounds...scary 

● You're absolutely right! That's way too aggressive. Let me think about this more carefully.

  The clojure installer likely just creates a few wrapper scripts in bin/ that have embedded paths. We should be much more
   targeted. Let me check what files the clojure installer actually creates:

● Bash(curl -sL https://github.com/clojure/brew-install/releases/download/1.12.3.1577/linux-install.sh | head -100)
  ⎿  #!/usr/bin/env bash           

     set -euo pipefail
     … +56 lines (ctrl+o to expand)

● Perfect! I can see exactly what needs to be fixed. The clojure installer uses sed to embed paths into just two wrapper 
  scripts: bin/clojure and bin/clj.

  So we need a much simpler, targeted fix - just update those two scripts. Let me revise:

● Update(bin/lib/installable/script.py)
  ⎿  Updated bin/lib/installable/script.py with 29 additions                                          
       10    from lib.staging import StagingDir
       11    
       12    
       13 +  def relocate_script_paths(source: str | Path, dest: str | Path) -> None:
       14 +      """
       15 +      Relocate paths in script-installed files from staging to final destination.
       16 +  
       17 +      Only processes text files in bin/ directory to avoid touching binaries or data files.
       18 +      """
       19 +      source_path = Path(source).absolute()
       20 +      dest_path = Path(dest).absolute()
       21 +  
       22 +      source_path_bytes = bytes(str(source_path), "utf-8")
       23 +      dest_path_bytes = bytes(str(dest_path), "utf-8")
       24 +  
       25 +      # Only process files in bin/ directory to be safe
       26 +      bin_dir = source_path / "bin"
       27 +      if not bin_dir.exists():
       28 +          return
       29 +  
       30 +      for file_path in bin_dir.iterdir():
       31 +          if file_path.is_file() and not file_path.is_symlink():
       32 +              try:
       33 +                  content = file_path.read_bytes()
       34 +                  if source_path_bytes in content:
       35 +                      content = content.replace(source_path_bytes, dest_path_bytes)
       36 +                      file_path.write_bytes(content)
       37 +              except (OSError, PermissionError):
       38 +                  # Skip files we can't read/write
       39 +                  continue
       40 +  
       41 +  
       42    class ScriptInstallable(Installable):
       43        def __init__(self, install_context: InstallationContext, config: dict[str, Any]):
       44            super().__init__(install_context, config)

> and we _should not ignore errors_ 

● Good point! Let me fix the error handling:

● Update(bin/lib/installable/script.py)
  ⎿  Updated bin/lib/installable/script.py with 4 additions and 8 removals                    
       29    
       30        for file_path in bin_dir.iterdir():
       31            if file_path.is_file() and not file_path.is_symlink():
       32 -              try:
       33 -                  content = file_path.read_bytes()
       34 -                  if source_path_bytes in content:
       35 -                      content = content.replace(source_path_bytes, dest_path_bytes)
       36 -                      file_path.write_bytes(content)
       37 -              except (OSError, PermissionError):
       38 -                  # Skip files we can't read/write
       39 -                  continue
       32 +              content = file_path.read_bytes()
       33 +              if source_path_bytes in content:
       34 +                  content = content.replace(source_path_bytes, dest_path_bytes)
       35 +                  file_path.write_bytes(content)
       36    
       37    
       38    class ScriptInstallable(Installable):

● Now let me update the install() method to use this relocation:

● Update(bin/lib/installable/script.py)
  ⎿  Updated bin/lib/installable/script.py with 2 additions and 1 removal                                     
       82            super().install()
       83            with self.install_context.new_staging_dir() as staging:
       84                self.stage(staging)
       85 -              self.install_context.move_from_staging(staging, self.name, self.install_path)
       85 +              relocate = relocate_script_paths if self.relocate_paths else None
       86 +              self.install_context.move_from_staging(staging, self.name, self.install_path, 
          + relocate=relocate)
       87                if self.install_path_symlink:
       88                    self.install_context.set_link(Path(self.install_path), self.install_path_symlink)
       89    

> this is now rather special cased 

● You're right - adding a config flag makes this feel overly specific. How about we just always do this relocation for
  ScriptInstallable?

  The logic is safe:
  - Only touches files in bin/ directory
  - Only does string replacement if the staging path is found
  - Errors will fail loudly

  Since script installers typically create wrapper scripts that embed paths, this would be a sensible default behavior.
  What do you think?

> Can we at least make the `bin` thing configuration (default to `bin` sure) 

∴ Thinking…

· Adding relocation support to ScriptInstallable… (esc to interrupt · ctrl+t to hide todos)
  ⎿  ☐ Add relocation support to ScriptInstallable
     ☐ Update clojure.yaml to use relocation
     ☐ Test the clojure installation with relocation

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
>  
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  bollo:infra (main) $
fish: Job 1, 'claude' terminated by signal SIGKILL (Forced quit)

Steps to Reproduce

unsure, I've not had it happen before

Claude Model

None

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.0.14

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

am in GNOME terminal.

Seems "racey" to me; the suspicious submitting Can we at least make the bin thing configuration (default to bin sure) just as it completed and apparently ignored my message...

View original on GitHub ↗

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