-
Notifications
You must be signed in to change notification settings - Fork 353
Add job cancellation support to the debugger command relay #1262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| --- | ||
| name: debug | ||
| description: Run commands inside a remote Docker container via the file-based command relay (tools/debugger). Use when the user says "run in Docker", "run on GPU", "debug remotely", "run test in container", "check nvidia-smi", "run pytest in Docker", or needs to execute any command inside a Docker container that shares the repo filesystem. Requires the user to have started server.sh inside the container first. | ||
| --- | ||
|
|
||
| # Remote Docker Debugger | ||
|
|
||
| Execute commands inside a Docker container from the host using the file-based command relay. | ||
|
|
||
| **Read `tools/debugger/CLAUDE.md` for full usage details** — it has the protocol, examples, and troubleshooting. | ||
|
|
||
| ## Quick Reference | ||
|
|
||
| ```bash | ||
| # Check connection | ||
| bash tools/debugger/client.sh status | ||
|
|
||
| # Connect to server (user must start server.sh in Docker first) | ||
| bash tools/debugger/client.sh handshake | ||
|
|
||
| # Run a command | ||
| bash tools/debugger/client.sh run "<command>" | ||
|
|
||
| # Long-running command (default timeout is 600s) | ||
| bash tools/debugger/client.sh --timeout 1800 run "<command>" | ||
|
|
||
| # Cancel the currently running command | ||
| bash tools/debugger/client.sh cancel | ||
|
|
||
| # Reconnect after server restart | ||
| bash tools/debugger/client.sh flush | ||
| bash tools/debugger/client.sh handshake | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| # Usage: | ||
| # bash client.sh handshake - Connect to server | ||
| # bash client.sh run <command...> - Run a command and print output | ||
| # bash client.sh cancel - Cancel the running command | ||
| # bash client.sh status - Check server status | ||
| # | ||
| # Options: | ||
|
|
@@ -91,6 +92,8 @@ case "$SUBCOMMAND" in | |
| # Generate a unique command ID (timestamp + PID to avoid collisions) | ||
| cmd_id="$(date +%s%N)_$$" | ||
|
|
||
| echo "[client] Running: $*" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid logging raw command text by default. Line 94 prints the full command, which can expose inline credentials/tokens in shell/CI logs. Log only 🔧 Proposed change- echo "[client] Running: $*"
+ if [[ "${DEBUGGER_LOG_COMMANDS:-0}" == "1" ]]; then
+ echo "[client] Running command id=$cmd_id: $*"
+ else
+ echo "[client] Running command id=$cmd_id"
+ fi🤖 Prompt for AI Agents |
||
|
|
||
| # Write the command file atomically (tmp + mv) | ||
| echo "$*" > "$CMD_DIR/$cmd_id.sh.tmp" | ||
| mv "$CMD_DIR/$cmd_id.sh.tmp" "$CMD_DIR/$cmd_id.sh" | ||
|
|
@@ -108,14 +111,32 @@ case "$SUBCOMMAND" in | |
| elapsed=$((elapsed + POLL_INTERVAL)) | ||
| if [[ $elapsed -ge $TIMEOUT ]]; then | ||
| echo "ERROR: Command timed out after ${TIMEOUT}s." | ||
| # Clean up the pending command | ||
| # Cancel the running command only if it is OUR command | ||
| if [[ -f "$RELAY_DIR/running" ]]; then | ||
| running_info=$(cat "$RELAY_DIR/running" 2>/dev/null) || true | ||
| running_id="${running_info%%:*}" | ||
| if [[ "$running_id" == "$cmd_id" ]]; then | ||
| echo "Sending cancel signal..." | ||
| echo "$cmd_id" > "$RELAY_DIR/cancel" | ||
|
Comment on lines
+119
to
+120
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Write
🔧 Proposed change- echo "$cmd_id" > "$RELAY_DIR/cancel"
+ tmp_cancel="$RELAY_DIR/cancel.$$"
+ echo "$cmd_id" > "$tmp_cancel"
+ mv "$tmp_cancel" "$RELAY_DIR/cancel"
...
- echo "$running_id" > "$RELAY_DIR/cancel"
+ tmp_cancel="$RELAY_DIR/cancel.$$"
+ echo "$running_id" > "$tmp_cancel"
+ mv "$tmp_cancel" "$RELAY_DIR/cancel"Also applies to: 199-200 🤖 Prompt for AI Agents |
||
| for _ in $(seq 1 10); do | ||
| [[ -f "$RELAY_DIR/running" ]] || break | ||
| sleep 1 | ||
| done | ||
|
Comment on lines
+121
to
+124
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not wait for the relay to go idle after cancelling one command. Both loops only wait for 🔧 Suggested shapewait_for_running_id_to_clear() {
local target_id="$1" timeout="$2" elapsed=0 current_info current_id
while [[ $elapsed -lt $timeout ]]; do
if [[ ! -f "$RELAY_DIR/running" ]]; then
return 0
fi
current_info=$(cat "$RELAY_DIR/running" 2>/dev/null || true)
current_id="${current_info%%:*}"
[[ "$current_id" != "$target_id" ]] && return 0
sleep "$POLL_INTERVAL"
elapsed=$((elapsed + POLL_INTERVAL))
done
return 1
}Apply the same check in both the timeout-cancel path and the Also applies to: 204-211 🤖 Prompt for AI Agents |
||
| fi | ||
| fi | ||
| # Clean up command and any orphaned result files | ||
| rm -f "$CMD_DIR/$cmd_id.sh" | ||
| rm -f "$RESULT_DIR/$cmd_id.exit" "$RESULT_DIR/$cmd_id.log" | ||
| exit 1 | ||
| fi | ||
| done | ||
|
|
||
| # Read and display results | ||
| exit_code=$(cat "$RESULT_DIR/$cmd_id.exit") | ||
| if ! [[ "$exit_code" =~ ^[0-9]+$ ]]; then | ||
| echo "WARNING: Invalid exit code '$exit_code', defaulting to 1." | ||
| exit_code=1 | ||
| fi | ||
| if [[ -f "$RESULT_DIR/$cmd_id.log" ]]; then | ||
| cat "$RESULT_DIR/$cmd_id.log" | ||
| fi | ||
|
|
@@ -139,6 +160,11 @@ case "$SUBCOMMAND" in | |
| else | ||
| echo "Handshake: not started" | ||
| fi | ||
| if [[ -f "$RELAY_DIR/running" ]]; then | ||
| echo "Running: $(cat "$RELAY_DIR/running")" | ||
| else | ||
| echo "Running: (idle)" | ||
| fi | ||
| if [[ -d "$CMD_DIR" ]]; then | ||
| pending=$(find "$CMD_DIR" -maxdepth 1 -type f -name '*.sh' 2>/dev/null | wc -l) | ||
| else | ||
|
|
@@ -148,6 +174,10 @@ case "$SUBCOMMAND" in | |
| ;; | ||
|
|
||
| flush) | ||
| if [[ -f "$RELAY_DIR/running" ]]; then | ||
| echo "ERROR: A command is currently running. Cancel it first or wait for it to finish." | ||
| exit 1 | ||
| fi | ||
| if [[ -d "$RELAY_DIR" ]]; then | ||
| # Clear handshake and command/result files, but keep server.ready | ||
| rm -f "$RELAY_DIR/client.ready" "$RELAY_DIR/handshake.done" | ||
|
|
@@ -159,12 +189,47 @@ case "$SUBCOMMAND" in | |
| fi | ||
| ;; | ||
|
|
||
| cancel) | ||
| # Check if there's a running command | ||
| if [[ -f "$RELAY_DIR/running" ]]; then | ||
| running_info=$(cat "$RELAY_DIR/running" 2>/dev/null) || true | ||
| running_id="${running_info%%:*}" | ||
| echo "Cancelling running command: $running_id" | ||
|
|
||
| # Write cancel signal with cmd_id so server can verify the target | ||
| echo "$running_id" > "$RELAY_DIR/cancel" | ||
|
|
||
| # Wait for the server to process the cancellation | ||
| elapsed=0 | ||
| while [[ -f "$RELAY_DIR/running" ]]; do | ||
| sleep "$POLL_INTERVAL" | ||
| elapsed=$((elapsed + POLL_INTERVAL)) | ||
| if [[ $elapsed -ge 30 ]]; then | ||
| echo "WARNING: Cancel signal sent but command still running after 30s." | ||
| exit 1 | ||
| fi | ||
| done | ||
| echo "Command cancelled." | ||
| else | ||
| echo "No command is currently running." | ||
| fi | ||
|
|
||
| # Report pending commands | ||
| if [[ -d "$CMD_DIR" ]]; then | ||
| pending=$(find "$CMD_DIR" -maxdepth 1 -type f -name '*.sh' 2>/dev/null | wc -l) | ||
| if [[ "$pending" -gt 0 ]]; then | ||
| echo "$pending pending command(s) in queue. Use 'flush' to clear them." | ||
| fi | ||
| fi | ||
| ;; | ||
|
|
||
| *) | ||
| echo "Usage: $0 [--relay-dir <path>] [--timeout <secs>] <subcommand>" | ||
| echo "" | ||
| echo "Subcommands:" | ||
| echo " handshake Connect to the server" | ||
| echo " run <cmd> Execute a command on the server" | ||
| echo " cancel Cancel the currently running command" | ||
| echo " status Check connection status" | ||
| echo " flush Clear the relay directory" | ||
| exit 1 | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -63,10 +63,19 @@ RESULT_DIR="$RELAY_DIR/result" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cleanup() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Shutting down..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Kill any running command (guard all reads with || true to prevent set -e | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # from aborting the trap and leaving stale marker files) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| running_pid=$(cut -d: -f2 "$RELAY_DIR/running" 2>/dev/null) || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$running_pid" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pkill -P "$running_pid" 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| kill "$running_pid" 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Kill any child processes in our process group | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pkill -P $$ 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$RELAY_DIR/server.ready" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$RELAY_DIR/handshake.done" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$RELAY_DIR/running" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$RELAY_DIR/cancel" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trap cleanup SIGINT SIGTERM | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -87,17 +96,28 @@ fi | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -rf "$RELAY_DIR" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mkdir -p "$CMD_DIR" "$RESULT_DIR" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Install modelopt in editable mode (skip if already editable-installed from WORKDIR) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if python -c " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import modelopt, os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert os.path.realpath(modelopt.__path__[0]).startswith(os.path.realpath('$WORKDIR')) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " 2>/dev/null; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Ensure modelopt is editable-installed from WORKDIR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| check_modelopt_local() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| python -c " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import modelopt, os, sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| actual = os.path.realpath(modelopt.__path__[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expected = os.path.realpath('$WORKDIR') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not actual.startswith(expected): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f'modelopt loaded from {actual}, expected under {expected}', file=sys.stderr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.exit(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " 2>&1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+105
to
+108
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify the prefix false-positive behavior vs boundary-safe check.
python - <<'PY'
import os
actual = "/workspace/repo2/modelopt"
expected = "/workspace/repo"
print("startswith:", actual.startswith(expected)) # incorrect acceptance
print("commonpath:", os.path.commonpath([actual, expected]) == expected) # correct rejection
PYRepository: NVIDIA/Model-Optimizer Length of output: 100 🏁 Script executed: # Examine the actual file to verify the code at lines 90-100 and 144-145
head -150 tools/debugger/server.sh | tail -70Repository: NVIDIA/Model-Optimizer Length of output: 2591 Use boundary-safe path validation and avoid logging sensitive command content. Line 96 uses Additionally, line 144 logs the full command content without filtering: cmd_content=$(cat "$cmd_file")
echo "[server] Executing command $cmd_id: $cmd_content"This exposes any secrets, API keys, or credentials present in command files to server logs, violating the guideline that sensitive information must not be hardcoded or logged. 🔧 Proposed changesFor path validation (lines 96-99): check_modelopt_local() {
- python -c "
+ WORKDIR_ENV=\"$WORKDIR\" python -c "
import modelopt, os, sys
actual = os.path.realpath(modelopt.__path__[0])
-expected = os.path.realpath('$WORKDIR')
-if not actual.startswith(expected):
+expected = os.path.realpath(os.environ['WORKDIR_ENV'])
+if os.path.commonpath([actual, expected]) != expected:
print(f'modelopt loaded from {actual}, expected under {expected}', file=sys.stderr)
sys.exit(1)
" 2>&1
}For command logging (lines 144-145), avoid logging the full command content: -echo "[server] Executing command $cmd_id: $cmd_content"
+echo "[server] Executing command $cmd_id"🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if check_modelopt_local >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] modelopt already editable-installed from $WORKDIR, skipping pip install." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Installing modelopt (pip install -e .[dev]) ..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (cd "$WORKDIR" && pip install -e ".[dev]") || { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] WARNING: pip install failed (exit=$?), continuing anyway." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (cd "$WORKDIR" && pip install -e ".[dev]") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! check_modelopt_local; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+99
to
+116
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Relevant server.sh lines:"
rg -n -C2 'export PYTHONPATH|check_modelopt_local|pip install -e' tools/debugger/server.sh
echo
repo_root="$(pwd)"
echo "Import resolution with PYTHONPATH set to the repo root:"
PYTHONPATH="$repo_root" python - <<'PY'
import importlib.util, os
spec = importlib.util.find_spec("modelopt")
print("PYTHONPATH =", os.environ["PYTHONPATH"])
print("spec is None:", spec is None)
if spec and spec.submodule_search_locations:
print("search locations =", list(spec.submodule_search_locations))
PYRepository: NVIDIA/Model-Optimizer Length of output: 1054
Line 84 exports The subprocess in Suggested fix check_modelopt_local() {
- python -c "
+ WORKDIR_ENV="$WORKDIR" PYTHONPATH="" python -I -c "
import modelopt, os, sys
actual = os.path.realpath(modelopt.__path__[0])
-expected = os.path.realpath('$WORKDIR')
+expected = os.path.realpath(os.environ['WORKDIR_ENV'])
if not actual.startswith(expected):
print(f'modelopt loaded from {actual}, expected under {expected}', file=sys.stderr)
sys.exit(1)
" 2>&1
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] ERROR: modelopt is not running from the local folder ($WORKDIR)." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Try: pip install -e '.[dev]' inside the container, then restart the server." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Install done." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -129,19 +149,90 @@ while true; do | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for cmd_file in "$CMD_DIR"/*.sh; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd_id="$(basename "$cmd_file" .sh)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Executing command $cmd_id..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Execute the command, tee stdout+stderr to console and result file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (cd "$WORKDIR" && bash "$cmd_file" 2>&1) | tee "$RESULT_DIR/$cmd_id.log" || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit_code=${PIPESTATUS[0]} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Guard against command files deleted by the client between glob expansion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # and processing (e.g., client timeout on a queued command) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ -f "$cmd_file" ]] || continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Atomic write of exit code (signal to client that result is ready) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd_id="$(basename "$cmd_file" .sh)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Tolerate file disappearing between guard and read (TOCTOU with client timeout) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd_content=$(cat "$cmd_file" 2>/dev/null) || continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Remove command file immediately after reading to prevent re-execution | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # and to avoid TOCTOU with client timeout deleting it during execution | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$cmd_file" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Executing command $cmd_id: $cmd_content" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Clear any stale cancel file from a previous timed-out client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$RELAY_DIR/cancel" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Create log file and stream output to server console via tail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : > "$RESULT_DIR/$cmd_id.log" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tail -f "$RESULT_DIR/$cmd_id.log" & | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tail_pid=$! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Run from cmd_content (not the file) since we already removed it | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (cd "$WORKDIR" && bash -c "$cmd_content") >> "$RESULT_DIR/$cmd_id.log" 2>&1 & | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd_pid=$! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Track the running command (ID and PID) — atomic write to prevent partial reads | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "$cmd_id:$cmd_pid" > "$RELAY_DIR/running.tmp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mv "$RELAY_DIR/running.tmp" "$RELAY_DIR/running" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Wait for completion or cancellation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cancelled="" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while kill -0 "$cmd_pid" 2>/dev/null; do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -f "$RELAY_DIR/cancel" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Verify cancel targets this command (reject empty or mismatched signals) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cancel_target=$(cat "$RELAY_DIR/cancel" 2>/dev/null) || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ "$cancel_target" != "$cmd_id" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$RELAY_DIR/cancel" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sleep "$POLL_INTERVAL" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Cancelling command $cmd_id (PID $cmd_pid)..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Send SIGTERM to children first, then parent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pkill -P "$cmd_pid" 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| kill "$cmd_pid" 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Wait up to 5s for graceful exit, then escalate to SIGKILL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _ in $(seq 1 5); do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| kill -0 "$cmd_pid" 2>/dev/null || break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sleep 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if kill -0 "$cmd_pid" 2>/dev/null; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Process $cmd_pid did not exit, sending SIGKILL..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pkill -9 -P "$cmd_pid" 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| kill -9 "$cmd_pid" 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wait "$cmd_pid" 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cancelled="true" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$RELAY_DIR/cancel" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[cancelled]" >> "$RESULT_DIR/$cmd_id.log" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Command $cmd_id cancelled." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sleep "$POLL_INTERVAL" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Determine exit code (|| exit_code=$? prevents set -e from killing the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # server when the command exits non-zero) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -n "$cancelled" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit_code=130 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit_code=0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wait "$cmd_pid" 2>/dev/null || exit_code=$? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Stop console streaming | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| kill "$tail_pid" 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wait "$tail_pid" 2>/dev/null || true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Write exit code BEFORE removing the running marker, so any observer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # that sees running disappear can immediately find the result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "$exit_code" > "$RESULT_DIR/$cmd_id.exit.tmp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mv "$RESULT_DIR/$cmd_id.exit.tmp" "$RESULT_DIR/$cmd_id.exit" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Remove the command file to mark it as processed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$cmd_file" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Now safe to remove markers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$RELAY_DIR/running" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rm -f "$RELAY_DIR/cancel" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "[server] Command $cmd_id finished (exit=$exit_code)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protocol section still describes the pre-buffered execution flow.
tools/debugger/server.shno longer runsbash <file>and deletes the.shfile at the end; it reads the file, removes it immediately, and executesbash -c "$cmd_content".tools/debugger/client.shalso writes the targetcmd_idinto.relay/cancel. This section should be updated so the documented on-disk state transitions match what users will actually see.🤖 Prompt for AI Agents