[BUG] Freezes terminal most of the time when trying to access anything remotely related to tty devices.

Resolved 💬 24 comments Opened Aug 21, 2025 by kimasplund Closed Mar 3, 2026

● Bash(stty -F /dev/ttyACM0 -a 2>/dev/null | head -5)
⎿  speed 9600 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;

-> freeze...

● Bash(sudo screen -L -Logfile c6_serial.log /dev/ttyACM0 115200 &)...

-> freeze...

ok that also freezes you. try just to access any tty device and see if you freeze

● Let me test with a simpler TTY device to confirm the issue:

● Bash(ls -la /dev/tty /dev/tty0 /dev/pts/0 2>/dev/null | head -3)
⎿  crw------- 1 kim tty 136, 0 Aug 20 12:24 /dev/pts/0
crw-rw-rw- 1 root tty 5, 0 Aug 21 11:18 /dev/tty
crw------- 1 root tty 4, 0 Aug 20 12:23 /dev/tty0

✶ Whisking… (esc to interrupt)

-> freeze...

View original on GitHub ↗

24 Comments

github-actions[bot] · 10 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/3964
  2. https://github.com/anthropics/claude-code/issues/5010
  3. https://github.com/anthropics/claude-code/issues/1742

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

andrewleech · 10 months ago

I just raised a new issue that does look like a variant of this one, so I'll close mine and add the details here

---

Environment

  • Platform (select one):
  • [X] Anthropic API
  • [ ] AWS Bedrock
  • [ ] Google Vertex AI
  • [ ] Other: <!-- specify -->
  • Claude CLI version: 1.0.92
  • Operating System: 24.04.2 LTS
  • Terminal: tmux/ssh -> Windows Terminal

Bug Description

While using the Bash tool to run tests that interact with hardware, both the hardware and Claude Code lock up and nee to be forcefully killed.

I'm using Cluade Code to build a hardware interfacing library; which boils down to sending ascii data to an embedded device (which is running micropython) over USB CDC (serial / tty), the device processes that data then sends a response back up the usb cdc to the host.

There's hardware tests in this library which Claude Clode has been running as part of the development cycle, (implement, build, test, etc) which had been working great, however more and more recently the device was getting locked up and Claude Code was also getting locked up, usually during a Bash tool use that was relating to the tty device under test.

The exact same tests scripts run directly from ssh/bash run just fine for 100's of cycles, however when Claude Code run the same script it usually locks up within one or two iterations.

When I say locked up, I mean text input no longer works and even the little anthoropic logo ticker that flickers while active, freezes.
At this point ctrl-c / escape doesn't have any impact. It will stay this way for hours if I leave it. I generally have to kill the tmux session and/or switch to a different terminal to pkill the claude instance.

While in this locked up state, I can connect to the hardware device with a separate terminal all (normally mpremote) but it too would appear unresponsive to all normal commands. I was able to confirm via a hardware debugger though that the target processor itself appeared to be running normally, it was running through it's normal main / repl loop still.

I've now discovered that unplugging the usb device generally unfreezes claude code.

Similarly, without unplugging the device I've found that I can use lsof to find the open handle to the serial device then use gdb to forcefully close that handle from within the claude program - this unblocks the hardware and allows the terminal/mpremote to communicate with it normally again.
<details>
<summary>Click to expand details</summary>

``` bash
$ sudo lsof | grep ttyACM0
claude 826926 corona 24r CHR 166,0 0t0 3554 /dev/ttyACM0
claude 826926 826927 node corona 24r CHR 166,0 0t0 3554 /dev/ttyACM0
claude 826926 826928 node corona 24r CHR 166,0 0t0 3554 /dev/ttyACM0
claude 826926 826929 node corona 24r CHR 166,0 0t0 3554 /dev/ttyACM0
claude 826926 826934 node corona 24r CHR 166,0 0t0 3554 /dev/ttyACM0
claude 826926 826935 node corona 24r CHR 166,0 0t0 3554 /dev/ttyACM0
claude 826926 826936 node corona 24r CHR 166,0 0t0 3554 /dev/ttyACM0
mpremote 839011 corona 3uW CHR 166,0 0t0 3554 /dev/ttyACM0

$ sudo gdb -p 826926
...
For help, type "help".
Type "apropos word" to search for commands related to "word".
Attaching to process 826926

(gdb) call close(24)
$1 = 0
(gdb) detach
Detaching from program: /home/corona/.nvm/versions/node/v22.16.0/bin/node, process 826926
[Inferior 1 (process 826926) detached]
(gdb) exit

</details>


I've been investigating this for quite a few days now and believe I've narrowed it down to a subprocess pipe blocked kind of issue; it actually looks the same to me now as a well known issue with python subprocess handling as described in:
*  https://lucadrf.dev/blog/python-subprocess-buffers/ 
* https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/

## Steps to Reproduce
I'm honestly not sure how to reproduce this without my harware / library project setup.
If it's "just" a subprocess stdio buffering issue then perhaps any subprocess with the right/wrong structure or timing of stdout would cause the same issue, but I'm not sure.

## Expected Behavior
Scripts / programs run though Claude Code Bash() tool behave the same way as if they're run from a "normal" ssh/bash session.

## Actual Behavior
Script / program is locking up both Claude Code and hardware.

## Additional Context
I'm happy to provide any further details or run tests if it would help.
dotdioscorea · 10 months ago

I have also been running into what sounds like the exact same issue. I am trying to get claude code to write a serial driver for a nordic development board, and python pytests to test its functionality.

Regardless of whether I am using the board's Jlink serial port or a separate FTDI cable, claude code will invariably freeze up within one or two interactions of the serial port device. Interestingly it seems to happen even if claude is not directly interacting with the port, eg running a python script to read from it. As with andrewleech, replugging the port frees up claude, but it will normally freeze up again shortly after.

While frozen, the working animation ascii thing stops, I cannot type, cannot ctrl c - nothing works. The moment I unplug the offending usb port, it continues where it left off. I am running on a laptop with debian installed, the issue happens if I run in the local terminal or ssh'ing into the laptop.

Its a shame, it renders claude code unusable for this sort of embedded work at the moment.

cptkoolbeenz · 10 months ago

+1 Hangs when trying to access the serial port through bash or Python.
One pseudo-workaround is to run CC in WSL, then have it write Python to be run from PowerShell.

andrewleech · 10 months ago

I'm thinking I should try an older version but haven't quite figured out just how old I'll need to go

dstude · 10 months ago

workaround I found handy: using firejail --private-dev claude and asking claude to run stuff through ssh

shamefully, i opened this dupe but hopefully eases the pain

andrewleech · 10 months ago
workaround I found handy: using firejail --private-dev claude and asking claude to run stuff through ssh

Ah yes, that's an (annoying) great idea. Similarly can probably use a docker container and/or separate computer and still tell claude to execute everything via ssh!

andrewleech · 9 months ago

In case it helps; when I use gdb to forcefully close the ttyACM0 file handle in the claude executable, claude crashes :-) this is the stack trace:

  #  claude[724610]: node::fs::ReadFileUtf8(const v8::FunctionCallbackInfo<v8::Value>&)::<lambda()> at ../src/node_file.cc:2606  
  #  Assertion failed: (0) == (uv_fs_close(nullptr, &req, file, nullptr))                                                        
                                                                                                                                 
----- Native stack trace -----                                                                                                   
                                                                                                                                 
 1: 0xf6ea57 node::Assert(node::AssertionInfo const&) [claude]                                                                   
 2: 0xf7623a  [claude]                                                                                                           
 3: 0xf8afcc  [claude]                                                                                                           
 4: 0x723d0fe0f745                                                                                                               
                                                                                                                                 
----- JavaScript stack trace -----                                                                                               
                                                                                                                                 
1: readFileSync (node:fs:442:20)                                                                                                 
2: readFileSync (file:///home/corona/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:719:6848)     
3: x9B (file:///home/corona/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:3563:9983)             
4: call (file:///home/corona/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:1724:978)             
5: ZO (file:///home/corona/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:1729:2967)              
6: file:///home/corona/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:1810:1333                   
7: processTicksAndRejections (node:internal/process/task_queues:105:5)                                                           
                                                                                                                                 
                                                                                                                                 
Aborted (core dumped)                                                                                                             
andrewleech · 9 months ago

It's still crashing on the latest calude, but not _every_ time it accesses a tty. So I'm still making decent progress with claude development that access hardware, but when it locks up I just need to kill the claude process, then claude -c and say either

the last test crashed, try again

or if I don't want to to lock up again from re-running the same thing I'll run the locking up test manually and paste it into claude ie

[corona@NUG tinyusb]$ ls /dev/serial/by-id/usb-MicroPython34003000 2>/dev/null && echo "✅ WB55 works with only base address fix!" || echo "❌ WB55 fails - structure fix w…"

/dev/serial/by-id/usb-MicroPython_Pyboard_Virtual_Comm_Port_in_FS_Mode_34003000-if00
✅ WB55 works with only base address fix!

To simplify killing just the claude that's stuck on a tty I got this basic script written: ~/kill-tty-process.sh

``` bash
#!/bin/bash

Get processes with open handles to ttyACM devices

processes=$(sudo lsof 2>/dev/null | grep '/dev/ttyACM' | awk '{print $2}' | sort -u)

if [ -z "$processes" ]; then
echo "No processes found with open ttyACM handles"
exit 0
fi

process_count=$(echo "$processes" | wc -l)

if [ "$process_count" -eq 1 ]; then
pid="$processes"
echo "Found single process $pid with open ttyACM handle, killing..."
kill -9 "$pid"
echo "Process $pid killed"
else
echo "Multiple processes found with open ttyACM handles:"
sudo lsof 2>/dev/null | grep '/dev/ttyACM' | awk '{print $1, $2, $9}' | sort -k2,2n
fi

seuros · 9 months ago

I improved your script to kill multiple session
It also kill hanging process in USB and cuaU.

This bug is pretty annoying especially when you code speak about a modem or device, and claude want to check it really connected. boom => stuck.

#!/usr/bin/env bash

# Get processes with open handles to ttyACM, ttyUSB, or cuaU devices
processes=$(sudo lsof 2>/dev/null | grep -E '/dev/(ttyACM|ttyUSB|cuaU)' | awk '{print $2}' | sort -u | grep -v '^$')

if [ -z "$processes" ]; then
    echo "No processes found with open ttyACM/ttyUSB/cuaU handles"
    exit 0
fi

echo "Found processes with open serial device handles:"
sudo lsof 2>/dev/null | grep -E '/dev/(ttyACM|ttyUSB|cuaU)' | awk '{print $1, $2, $9}' | sort -k2,2n

echo "Killing all processes..."
for pid in $processes; do
    if sudo kill -9 "$pid" 2>/dev/null; then
        echo "Killed process $pid"
    else
        echo "Failed to kill process $pid"
    fi
done
andrewleech · 9 months ago

User:

I've now found the serial port issue is in the claude executable after it tries to open a /dev/ttyX port file as per this stack trace:

#  claude[724610]: node::fs::ReadFileUtf8(const v8::FunctionCallbackInfo<v8::Value>&)::<lambda()> at ../src/node_file.cc:2606
  #  Assertion failed: (0) == (uv_fs_close(nullptr, &req, file, nullptr))

----- Native stack trace -----

 1: 0xf6ea57 node::Assert(node::AssertionInfo const&) [claude]
 2: 0xf7623a  [claude]
 3: 0xf8afcc  [claude]
 4: 0x723d0fe0f745

----- JavaScript stack trace -----

1: readFileSync (node:fs:442:20)
2: readFileSync (file://~/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:719:6848)
3: x9B (file://~/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:3563:9983)
4: call (file://~/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:1724:978)
5: ZO (file://~/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:1729:2967)
6: file://~/.nvm/versions/node/v22.16.0/lib/node_modules/@anthropic-ai/claude-code/cli.js:1810:1333
7: processTicksAndRejections (node:internal/process/task_queues:105:5)


Aborted (core dumped)

Assistant Response:

The crash is caused by claude using fs.readFileSync on a TTY device. When Node.js reads a TTY character device, the file descriptor can enter a state where uv_fs_close fails to close it, triggering the assertion failure.

This is the exact TTY lockup scenario described in the safety protocol. The issue is:

  1. readFileSync at node:fs:442:20 opens /dev/ttyX
  2. The read operation on the TTY device causes the file descriptor to stall
  3. Node.js attempts to close the fd with uv_fs_close
  4. Close fails (returns non-zero), triggering assertion at node_file.cc:2606

The CLI should not be directly reading TTY devices with synchronous file operations. If it needs to interact with serial devices, it should either:

  • Use proper serial port libraries (like serialport)
  • Use the socat wrapper pattern for TTY access
  • Skip TTY devices when doing file system enumeration

You may want to report this as a bug at https://github.com/anthropics/claude-code/issues - the CLI appears to be scanning or reading files without filtering out character devices.

As an immediate workaround, you could use your new script to kill the stuck process before it gets to the crash point, or restrict the CLI's access to /dev/tty* devices.

---

Solution Request

User:

the issue has already been raised and I have been using the script to kill the app when it happens, but that slows down progress. would it be possible to add a runtime monkey patch or modify an intermediate library or even node internal function to forcefully filter out / block calls of readFileSync when the target is a tty device?

Assistant Response:

I'll investigate the feasibility of intercepting readFileSync calls to TTY devices. Let me explore the options.

[Investigated Claude CLI structure and found it's a large bundled JavaScript file]

I can see the CLI is a large bundled JavaScript file. Based on the stack trace and the TTY safety protocol, I can create a runtime monkey patch to intercept fs.readFileSync calls before they reach TTY devices.

Solution: Node.js Preload Module

I'll create:

  1. TTY-safe wrapper module (tty-protect.js) that monkey-patches fs.readFileSync
  2. Wrapper script (claude-safe) that loads the protection before launching the CLI

The monkey patch will:

  • Intercept all fs.readFileSync calls
  • Check if the target is a character device matching /dev/tty*
  • Return empty string or throw a safe error instead of reading the device
  • Pass through all other file operations normally

Implementation Details:

tty-protect.js:

  • Hook into fs.readFileSync and fs.readSync
  • Use fs.statSync to check if target is a character device
  • Only block if path starts with /dev/tty AND is a character device
  • Handle edge cases (file descriptors, non-existent paths)

claude-safe wrapper:

  • Use NODE_OPTIONS=--require to inject the monkey patch
  • Execute the real CLI with all arguments passed through
  • No changes to the CLI executable itself

This approach:

  • Works with any CLI version (survives updates)
  • Doesn't modify the original executable
  • Can be easily disabled if needed
  • Prevents the assertion failure at the Node.js level

Implementation Plan:

  1. Write tty-protect.js preload module that monkey-patches fs.readFileSync and fs.readSync to detect and block character device reads on /dev/tty* paths
  2. Create claude-safe wrapper script that loads the protection module via NODE_OPTIONS before launching the CLI
  3. Make the wrapper executable and test

The protection will intercept TTY device reads before they cause the Node.js assertion failure, while allowing all other file operations to work normally.

---

Implementation Phase

Assistant Actions:

Created File 1: ~/tty-protect.js

Node.js preload module that:

  • Saves original fs.readFileSync and fs.readSync functions
  • Implements isTTYDevice() function that:
  • Checks if path is a string starting with /dev/tty
  • Uses fs.statSync to verify it's a character device
  • Returns false for file descriptors or non-TTY paths
  • Wraps fs.readFileSync to:
  • Call isTTYDevice() on the target path
  • If blocked, logs a warning message
  • Returns empty string (for text) or empty Buffer (for binary)
  • Otherwise calls the original function
  • Wraps fs.readSync (primarily protected at readFileSync level)
  • Logs when the module is loaded

Key Features:

  • Non-invasive: returns empty data instead of throwing errors
  • Informative: logs blocked attempts for debugging
  • Smart: only checks paths starting with /dev/tty
  • Safe: handles edge cases gracefully

Created File 2: ~/claude-safe

Bash wrapper script that:

  • Locates itself to find the TTY protection module
  • Defines path to the real Claude CLI
  • Checks both files exist before proceeding
  • Sets NODE_OPTIONS to preload the protection module
  • Executes the real CLI with all arguments via exec node

Features:

  • Transparent: passes all arguments to the real CLI
  • Robust: checks for missing components
  • Preserves existing NODE_OPTIONS if set
  • Uses exec for proper process replacement

Usage Instructions

To use the protected CLI:
# Direct invocation
~/claude-safe

# Or create an alias in ~/.bashrc or ~/.zshrc
alias claude='~/claude-safe'

# Or create a symlink
sudo ln -s ~/claude-safe /usr/local/bin/claude-safe

!claude-tty-safe.zip

---
Summary: With the above runtime patch I think the problem is "resolved" in that I have not have any further lockups in the last day or so with hardware in the loop testing! I do see some exception stack trace printouts on the claude code screen occasionally, every time it tries to talk to a tty port, but claude code seems to keep working like normal no worries. The stack traces are not in the conversation feed, the kind of print over the top but then disappear once I continue the conversion - they don't seem to even interrupt the actual claude workings.

github-actions[bot] · 7 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

andrewleech · 7 months ago

I've had the problem re-occur in the last few days when I ran without my hack/patch above.

dnery · 6 months ago

+1 I've had 3 sessions freeze up on me the last few days. Running on macOS Tahoe, using wezterm and fish shell.

aleksclark · 6 months ago

Happening here - arch linux (btw), zsh, urxvt. Trying to do stuff with ttyUSB1

therealladiesman217 · 6 months ago

Also happening for me...
Fedora, Bash/Zsh

heeen · 6 months ago

this also happens very frequently but not 100% of the time while developing a hid driver, claude keeps opening /dev/hidrawN blocking itself. I am suspecting this is some kind of pre-emptive read of either files written to by the subprocess or file paths printed by the subprocess to either monitor for filechanges and inform the agent or drive the code rollback feature.

heeen · 6 months ago

I'll leave my Claude's findings here for whomever might find it useful:

Root Cause Found

After bash execution, the code at line 454711 (beautified source) calls ctB() which uses Haiku LLM to extract file paths from stdout. These paths are then passed to M5.call() (the Read tool) at line 454720.

If paths like /dev/hidraw0 or /dev/ttyACM0 appear in bash output, they get passed to readFileSync()FREEZE

The problematic code flow:

Bash executes → ctB() extracts paths via Haiku → M5.call() reads each path
                                                        ↓
                                             /dev/hidraw0 → readFileSync → FREEZE

Fix (inject before the M5.call):

if (_A.startsWith("/dev/")) {
  console.warn("[dev-protect] Skipping device path from bash output:", _A);
  J.delete(_A);
  continue;
}
await M5.call({ file_path: _A }, Q);  // Only reached for non-device paths

Source locations (in beautified cli.js):

  • ctB() function: line 281759 - uses Haiku to extract file paths from bash output
  • M5.call(): line 454720 - calls the Read tool on extracted paths
  • M5 = Read tool (var X3 = "Read" at line 170375)
  • W9 = "Bash" tool name (line 163084)
  • Z9 = Bash tool definition (line 454535)
heeen · 6 months ago

Patch Script

I created a patch script that fixes this issue by modifying the installed CLI:

Gist: https://gist.github.com/heeen/b6ced51aeac9da1b315776f38752c4ff

How it works

The script uses Babel to parse the minified cli.js, finds the code pattern where file paths extracted from bash output are passed to the Read tool, and injects a check to skip /dev/* paths.

Usage

# Install dependencies
npm install @babel/parser @babel/traverse @babel/generator @babel/types

# Download and run
curl -O https://gist.githubusercontent.com/heeen/b6ced51aeac9da1b315776f38752c4ff/raw/patch-cli.js
node patch-cli.js          # Apply patch
node patch-cli.js --restore # Restore original

The patch adds this check before the M5.call() (Read tool):

if (_A.startsWith("/dev/")) { continue; }

This prevents Claude from trying to readFileSync() on device files like /dev/hidraw0 or /dev/ttyACM0 that appear in bash output.

andrewleech · 5 months ago

@heeen I'm not sure if you saw my runtime patch above in https://github.com/anthropics/claude-code/issues/6237#issuecomment-3393668561 I've been using it for a few months and it's been working well. Not sure if it covers hid as well though. It looks like yours is more targetted though, have you had any further lock-ups while running it?

APIUM · 5 months ago

I liked the deployment of @andrewleech 's fix, but it doesn't work with the new native Claude Code as it uses NODE_OPTIONS preload modules. Have updated it to use LD_PRELOAD, it hasn't crashed for me yet but I've one just started using it today.

Same deployment, I put it in ~/.local/bin, give it chmod +x and then just run claude-safe instead of claude. You'll want gcc installed. Only tested on Linux, should work on MacOS I'd think.

claude-tty-safe-v0.2.0.zip

Edit: Updated to fix error with LD_PRELOAD being inherited into child processes such as gcc and causing trouble

github-actions[bot] · 4 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

cptkoolbeenz · 4 months ago

It seems like this has gotten better/fixed? I haven't had it hang on tty access recently.

github-actions[bot] · 4 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.