iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🔨

Fixing Claude Code Execution Hangs in Cursor: A Wrapper Script Solution

に公開

For those who just want the conclusion

  • Running claude -p via Cursor Agent from the chat hangs indefinitely.
  • It works with a pipe (echo ... | claude) → Behavior changes depending on whether stdout/stderr is a TTY.
  • Solved with a wrapper that uses nohup + output redirection to point the process output to a file.
  • Wrapper script code (same as the sample in this article).

Background: What I wanted to achieve

Cursor Agent and Claude Code operate with different models and contexts. If so, by having the Agent in a Cursor chat thread run claude -p when told "ask Claude Code about this," I should be able to create a pseudo-multi-agent configuration.

For example:

  • Assigning code implementation to Cursor Agent while asking Claude Code for reviews or second opinions.
  • Persisting context on the Claude Code side with -c (session continuation) and submitting tasks incrementally.

Of course, it works if you manually open a terminal pane in Cursor and run claude -p. However, that doesn't allow for seamless collaboration within the conversation flow with the Cursor Agent. My intention was to have the Agent execute it via the terminal to get a second opinion or a review.

While trying to verify this, I ran into an issue where I couldn't receive a response from claude -p at all when executed via the Agent.

Environment

Item Version
macOS 15.7.3 (24G419)
Cursor 2.4.28 (arm64)
Claude CLI 2.1.31 (Claude Code)
Node.js 18.17.1

What Happens

When you have Cursor Agent execute claude -p in the terminal, it hangs without returning any output. The same behavior has been confirmed when running it directly from the cursor-agent command (Cursor CLI).

# When running via terminal execution from Cursor Agent in chat
$ claude -p "Hello, respond with OK"
# → No output after waiting more than 60 seconds. Only option is to kill.

# Similar behavior when running directly via cursor-agent command
$ cursor-agent claude -p "Hello, respond with OK"
# → Hangs in the same way

On the other hand, it works normally when passed from standard input via a pipe.

$ echo "Hello, respond with OK" | claude --output-format text
OK
# → Responds in about 5 seconds

If you run it directly from an external terminal (such as WezTerm), -p works without any problems.

Why it works with a pipe

It is likely that the input detection logic on the CLI side differs between the -p flag and a pipe.

  • Pipe: stdin is detected as a pipe via isatty(0), etc. → Reads purely as a stream.
  • -p flag: stdin remains a TTY, and it switches to non-interactive mode via the flag → TTY control setup is executed.

The terminal operated by Cursor Agent (and the cursor-agent command) runs in a PTY environment that differs from a normal one, leading to the assumption that a TTY control conflict is occurring in the latter path.

Solution: nohup wrapper

Redirect stdout/stderr of the process to a file using output redirection to bypass TTY detection on the CLI side.

Script

claude-safe
#!/bin/bash
# claude-safe - A wrapper to safely execute Claude CLI via Cursor Agent
#
# Usage:
#   claude-safe -p "prompt" --output-format text
#   claude-safe -c -p "session continuation"
#   DEBUG=1 claude-safe -p "with debug output"

set -euo pipefail

# Debug mode
DEBUG=${DEBUG:-0}

debug_log() {
    if [ "$DEBUG" = "1" ]; then
        echo "[DEBUG] $*" >&2
    fi
d}

# Output file
OUTPUT_DIR="${HOME}/.claude_wrapper"
mkdir -p "$OUTPUT_DIR"
OUTPUT_FILE="${OUTPUT_DIR}/output_$$.txt"
ERROR_FILE="${OUTPUT_DIR}/error_$$.txt"

# Cleanup
cleanup() {
    debug_log "Cleaning up..."
    rm -f "$OUTPUT_FILE" "$ERROR_FILE"
}
trap cleanup EXIT

debug_log "Running: claude $*"
debug_log "Output file: $OUTPUT_FILE"

# Run Claude (process isolation - macOS compatible)
# Process isolation via nohup and background execution
nohup claude "$@" > "$OUTPUT_FILE" 2> "$ERROR_FILE" &
CLAUDE_PID=$!

# Wait for process completion (capture exit code while avoiding immediate termination with set -e)
wait $CLAUDE_PID && EXIT_CODE=0 || EXIT_CODE=$?

debug_log "Exit code: $EXIT_CODE"

# Output
cat "$OUTPUT_FILE"
if [ -s "$ERROR_FILE" ]; then
    cat "$ERROR_FILE" >&2
fi

exit $EXIT_CODE

Setup

# Place the script (anywhere in your PATH)
cp claude-safe ~/.local/bin/claude-safe
chmod +x ~/.local/bin/claude-safe

Why this works

Cursor Agent terminal (unique PTY environment)
  └─ claude-safe (wrapper)
       └─ nohup claude ... > file 2> file & (stdout/stderr to file → non-TTY determination)
            └─ API call → Write to temporary file
       └─ wait → cat → Display to standard output

The key point here is the output redirection using > "$OUTPUT_FILE" 2> "$ERROR_FILE". By directing Claude CLI's stdout/stderr to a file instead of a TTY, the CLI's TTY detection (e.g., isatty()) determines it is "non-TTY," skipping the TTY control setup. This is the same logic as why it works with a pipe.

While nohup itself is a command to make the process ignore SIGHUP signals and isn't primarily for TTY detachment, the core effective factor here is making it non-TTY through redirection. Strictly speaking, it might work with just redirection + background execution without nohup, but I've included it for safety, including HUP resistance.

Note that it hasn't been confirmed which file descriptor (stdin/stdout/stderr) the CLI's internal TTY check is performed against. The workaround in this article is a practical one that proved effective in this environment.

Operation Verification

Here are the comparison results via the terminal running within a Cursor thread (verified for reproducibility across multiple runs).

claude -p (Hangs)

$ claude -p "Hello! This is a connectivity test. Please respond with OK."
# → No output after 90 seconds. Forced termination with kill.

✅ Via claude-safe (Normal operation)

$ claude-safe -p "Hello! This is a connectivity test. Please respond with OK."
OK - Claude Opus 4.5
# → Responds in about 6 seconds, exit code 0

Comparison

Execution Method Execution Environment Result Time Taken
claude -p "..." Via Cursor Agent ❌ Hangs Over 60 sec (no timeout)
echo "..." | claude --output-format text Via Cursor Agent ✅ Works Approx. 5 sec
claude-safe -p "..." Via Cursor Agent ✅ Works Approx. 6 sec
claude -p "..." External Terminal (WezTerm) ✅ Works Approx. 5 sec

While it also works via a pipe, I believe the wrapper approach is more practical as it allows you to use the -p suite of options (such as session continuation with -c, --allowedTools, etc.) as they are.

Conclusion

  • The reason claude -p hangs via Cursor Agent is likely due to TTY/PTY control conflicts (speculative).
  • It can be solved by using a wrapper that redirects output to a file, thereby bypassing the CLI's TTY detection.
  • The essence of the workaround is the non-TTY status achieved through redirection, not nohup itself.
  • The fact that it works via a pipe supports the hypothesis that TTY control is the underlying cause.

I hope this serves as a helpful reference for anyone looking to try something similar.

Discussion