[GH-ISSUE #36] Ctrl-C cannot interrupt foreground processes in aenv interactive shell #18

Open
opened 2026-10-29 14:18:24 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @LSX-s-Software on GitHub (Jul 28, 2026).
Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/36

Summary

Foreground processes started from an interactive aenv start / aenv connect shell cannot reliably be interrupted with Ctrl-C. The terminal remains attached to the foreground process and appears hung until the process is killed from another session or the sandbox is deleted.

Observed on current main at 6296bc4be7.

Reproduction

  1. Start or connect to a sandbox using the native CLI:

    aenv start <template>
    # or
    aenv connect <sandbox-id>
    
  2. In the interactive sandbox shell, run a long-lived foreground process:

    python3 -m http.server 9000
    
  3. Press Ctrl-C.

Actual behavior

  • The Python process continues running.
  • The shell prompt does not return.
  • Repeated Ctrl-C input has no visible effect.
  • The attached aenv terminal appears stuck.
  • Recovery requires killing the process from another terminal, terminating the local aenv client, or deleting the sandbox.

The same class of problem can affect other foreground processes such as sleep or development servers.

Expected behavior

Ctrl-C in an aenv interactive shell should have normal terminal semantics: send SIGINT to the foreground process group inside the sandbox and return control to the shell when that process exits.

Disconnecting the local client should also not require deleting the sandbox, and the local terminal must always be restored from raw mode.

Initial analysis

crates/aenv/src/commands/connect.rs enters terminal raw mode through RawModeGuard. In raw mode, the local terminal does not generate SIGINT for the aenv process. Instead, Ctrl-C is read as byte 0x03 and forwarded as PTY input through StreamInput / SendInput.

The CLI does not currently install a local Ctrl-C handler or explicitly send SIGINT through envd. It relies entirely on the remote PTY path to interpret 0x03 and signal the foreground process group. In the observed case, that does not happen reliably, leaving the CLI waiting for the foreground process indefinitely.

The current process protocol also only exposes SIGTERM and SIGKILL in the Signal enum, not SIGINT:

enum Signal {
  SIGNAL_UNSPECIFIED = 0;
  SIGNAL_SIGTERM = 15;
  SIGNAL_SIGKILL = 9;
}

There may also be a recovery-path issue: control-only input such as 0x03 is not treated as input that should produce output by should_expect_output, so a lost or stalled Ctrl-C may not trigger the same input/output recovery logic as printable input.

Suggested direction

  • Make Ctrl-C reliably interrupt the remote foreground process group.
  • If PTY byte forwarding alone cannot guarantee this, add SIGINT support to the process signal API and/or an explicit terminal-control mechanism.
  • Preserve ordinary Ctrl-C behavior for interactive shells without terminating the sandbox.
  • Consider adding a documented local detach escape sequence so a broken remote PTY cannot trap the user in raw mode.
  • Ensure the input recovery path does not silently lose terminal control bytes.

Acceptance criteria

  • Running python3 -m http.server ... in aenv connect and pressing Ctrl-C stops the server and returns to the shell prompt.
  • The same works for sleep 3600 and similar foreground processes.
  • Ctrl-C still works after input/output stream reconnect or recovery.
  • The sandbox remains running after interruption.
  • Exiting or forcibly detaching the client restores the local terminal mode.
Originally created by @LSX-s-Software on GitHub (Jul 28, 2026). Original GitHub issue: https://github.com/kvcache-ai/AgentENV/issues/36 ## Summary Foreground processes started from an interactive `aenv start` / `aenv connect` shell cannot reliably be interrupted with Ctrl-C. The terminal remains attached to the foreground process and appears hung until the process is killed from another session or the sandbox is deleted. Observed on current `main` at 6296bc4be7ad79eb3a278eb5264ef011c341adf5. ## Reproduction 1. Start or connect to a sandbox using the native CLI: ```bash aenv start <template> # or aenv connect <sandbox-id> ``` 2. In the interactive sandbox shell, run a long-lived foreground process: ```bash python3 -m http.server 9000 ``` 3. Press Ctrl-C. ## Actual behavior - The Python process continues running. - The shell prompt does not return. - Repeated Ctrl-C input has no visible effect. - The attached `aenv` terminal appears stuck. - Recovery requires killing the process from another terminal, terminating the local `aenv` client, or deleting the sandbox. The same class of problem can affect other foreground processes such as `sleep` or development servers. ## Expected behavior Ctrl-C in an `aenv` interactive shell should have normal terminal semantics: send SIGINT to the foreground process group inside the sandbox and return control to the shell when that process exits. Disconnecting the local client should also not require deleting the sandbox, and the local terminal must always be restored from raw mode. ## Initial analysis `crates/aenv/src/commands/connect.rs` enters terminal raw mode through `RawModeGuard`. In raw mode, the local terminal does not generate SIGINT for the `aenv` process. Instead, Ctrl-C is read as byte `0x03` and forwarded as PTY input through `StreamInput` / `SendInput`. The CLI does not currently install a local Ctrl-C handler or explicitly send SIGINT through envd. It relies entirely on the remote PTY path to interpret `0x03` and signal the foreground process group. In the observed case, that does not happen reliably, leaving the CLI waiting for the foreground process indefinitely. The current process protocol also only exposes SIGTERM and SIGKILL in the `Signal` enum, not SIGINT: ```proto enum Signal { SIGNAL_UNSPECIFIED = 0; SIGNAL_SIGTERM = 15; SIGNAL_SIGKILL = 9; } ``` There may also be a recovery-path issue: control-only input such as `0x03` is not treated as input that should produce output by `should_expect_output`, so a lost or stalled Ctrl-C may not trigger the same input/output recovery logic as printable input. ## Suggested direction - Make Ctrl-C reliably interrupt the remote foreground process group. - If PTY byte forwarding alone cannot guarantee this, add SIGINT support to the process signal API and/or an explicit terminal-control mechanism. - Preserve ordinary Ctrl-C behavior for interactive shells without terminating the sandbox. - Consider adding a documented local detach escape sequence so a broken remote PTY cannot trap the user in raw mode. - Ensure the input recovery path does not silently lose terminal control bytes. ## Acceptance criteria - Running `python3 -m http.server ...` in `aenv connect` and pressing Ctrl-C stops the server and returns to the shell prompt. - The same works for `sleep 3600` and similar foreground processes. - Ctrl-C still works after input/output stream reconnect or recovery. - The sandbox remains running after interruption. - Exiting or forcibly detaching the client restores the local terminal mode.
Author
Owner

@Jainil-Gosalia commented on GitHub (Jul 29, 2026):

Potential Fix:

The fix will address three issues in crates/aenv/src/commands/connect.rs and the proto file. First, it will add SIGNAL_SIGINT = 2 to the Signal enum in thirdparty/envd/proto/process.proto and regenerate the stubs. Second, in maintain_stream_input, it will intercept byte 0x03 (Ctrl-C) and issue a SendSignal(SIGINT) unary RPC instead of forwarding it as PTY input, bypassing the unreliable remote PTY discipline. Third, it will expand should_expect_output to include 0x03 so Ctrl-C output triggers stall-detection recovery, and add a Ctrl-] escape sequence to drop raw mode without needing a remote response. The sandbox will stay alive (SIGINT, not SIGKILL) and terminal restoration will be verified on all exit paths. The external envd server changes will remain out of scope in the e2b-dev/infra fork.

<!-- gh-comment-id:5124331513 --> @Jainil-Gosalia commented on GitHub (Jul 29, 2026): **Potential Fix:** The fix will address three issues in crates/aenv/src/commands/connect.rs and the proto file. First, it will add SIGNAL_SIGINT = 2 to the Signal enum in thirdparty/envd/proto/process.proto and regenerate the stubs. Second, in maintain_stream_input, it will intercept byte 0x03 (Ctrl-C) and issue a SendSignal(SIGINT) unary RPC instead of forwarding it as PTY input, bypassing the unreliable remote PTY discipline. Third, it will expand should_expect_output to include 0x03 so Ctrl-C output triggers stall-detection recovery, and add a Ctrl-] escape sequence to drop raw mode without needing a remote response. The sandbox will stay alive (SIGINT, not SIGKILL) and terminal restoration will be verified on all exit paths. The external envd server changes will remain out of scope in the e2b-dev/infra fork.
Author
Owner

@LSX-s-Software commented on GitHub (Aug 6, 2026):

Updated analysis based on repository history

This supersedes the earlier Potential Fix comment. The current evidence does not support adding SIGINT to the vendored envd protocol as the first fix.

Historical context

The original native CLI used sequential unary SendInput requests. After a send failure, its stdin pump stopped permanently.

Commit f603d754 changed input to StreamInput as part of a larger terminal recovery state machine. Its purpose was to recover from transient envd/proxy disconnections, reconnect output, and exit cleanly when the sandbox was deleted or envd remained unreachable. I found no evidence that the change was made for Ctrl-C semantics or measured input performance.

Current suspected failure mode

The current implementation has several delivery gaps consistent with this bug:

  • route_input() treats a successful write to the local stream_tx channel as success, but that does not confirm that envd received or wrote the bytes to the PTY.
  • should_expect_output() excludes control-only input such as Ctrl-C (0x03), so a lost Ctrl-C does not activate the input/output stall recovery path.
  • send_recovery_input() drains all remaining queued stdin after sending one recovery request, which can discard repeated control input.

These findings show an AgentENV-specific input reliability risk, but they do not yet prove which transport failure occurs in the reported reproduction.

Why SIGINT RPC is not the current direction

Ctrl-C should normally remain PTY input byte 0x03. The remote terminal driver then sends SIGINT to the PTY foreground process group.

Adding SIGINT only to the vendored client proto would not update the deployed envd server. Also, the existing process selector identifies the shell process, not necessarily the foreground job process group, so sending SIGINT to that PID would not preserve normal terminal semantics.

Proposed validation

  1. Add a direct envd PTY regression test: start an interactive shell, run sleep 3600, send PTY byte 0x03, and verify that the foreground command stops while the shell remains usable.
  2. Add an aenv/proxy integration test covering the same sequence through the current StreamInput path.
  3. Add a recovery-path test that forces input reconnection and verifies that Ctrl-C and subsequent input are not discarded.

If the direct envd test passes but the aenv/proxy test fails, the fix belongs in the aenv input transport/recovery layer.

A likely client-only fix is to use batched unary SendInput inside the current session state machine. This must preserve the reconnect behavior introduced by f603d754; simply reverting to the old stdin pump would reintroduce the bug where one transient send failure permanently disables input.

Keeping StreamInput is also possible, but reliable delivery would require an acknowledgement or an equivalent ordering/delivery boundary. Merely adding 0x03 to should_expect_output() would improve recovery detection but would not guarantee delivery of the first Ctrl-C.

<!-- gh-comment-id:5202763487 --> @LSX-s-Software commented on GitHub (Aug 6, 2026): ## Updated analysis based on repository history This supersedes the earlier **Potential Fix** comment. The current evidence does not support adding SIGINT to the vendored envd protocol as the first fix. ### Historical context The original native CLI used sequential unary `SendInput` requests. After a send failure, its stdin pump stopped permanently. Commit [`f603d754`](https://github.com/kvcache-ai/AgentENV/commit/f603d75495ad95e06eb0a26349cb2321868f9d47) changed input to `StreamInput` as part of a larger terminal recovery state machine. Its purpose was to recover from transient envd/proxy disconnections, reconnect output, and exit cleanly when the sandbox was deleted or envd remained unreachable. I found no evidence that the change was made for Ctrl-C semantics or measured input performance. ### Current suspected failure mode The current implementation has several delivery gaps consistent with this bug: - `route_input()` treats a successful write to the local `stream_tx` channel as success, but that does not confirm that envd received or wrote the bytes to the PTY. - `should_expect_output()` excludes control-only input such as Ctrl-C (`0x03`), so a lost Ctrl-C does not activate the input/output stall recovery path. - `send_recovery_input()` drains all remaining queued stdin after sending one recovery request, which can discard repeated control input. These findings show an AgentENV-specific input reliability risk, but they do not yet prove which transport failure occurs in the reported reproduction. ### Why SIGINT RPC is not the current direction Ctrl-C should normally remain PTY input byte `0x03`. The remote terminal driver then sends SIGINT to the PTY foreground process group. Adding SIGINT only to the vendored client proto would not update the deployed envd server. Also, the existing process selector identifies the shell process, not necessarily the foreground job process group, so sending SIGINT to that PID would not preserve normal terminal semantics. ### Proposed validation 1. Add a direct envd PTY regression test: start an interactive shell, run `sleep 3600`, send PTY byte `0x03`, and verify that the foreground command stops while the shell remains usable. 2. Add an aenv/proxy integration test covering the same sequence through the current `StreamInput` path. 3. Add a recovery-path test that forces input reconnection and verifies that Ctrl-C and subsequent input are not discarded. If the direct envd test passes but the aenv/proxy test fails, the fix belongs in the aenv input transport/recovery layer. A likely client-only fix is to use batched unary `SendInput` inside the current session state machine. This must preserve the reconnect behavior introduced by `f603d754`; simply reverting to the old stdin pump would reintroduce the bug where one transient send failure permanently disables input. Keeping `StreamInput` is also possible, but reliable delivery would require an acknowledgement or an equivalent ordering/delivery boundary. Merely adding `0x03` to `should_expect_output()` would improve recovery detection but would not guarantee delivery of the first Ctrl-C.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kvcache-ai/AgentENV#18
No description provided.