Skip to content

bug: sandbox exec exits 0 when the event stream ends without an exit status #2732

Description

@Dongni-Yang

Agent Diagnostic

Investigated from a source read of main at 245fe275, not from a live cluster (this host cannot run a gateway — glibc 2.31, below the 2.32 minimum). Searched existing issues for sandbox exec exit code and exit status stream: #1046 and #828 describe the CLI hanging after a complete response, and #1990 (closed) describes an indefinite hang. This report is the opposite outcome — the CLI terminates and reports success — so I am filing it separately rather than commenting there.

Description

When an ExecSandbox event stream ends without ever delivering an Exit event, the CLI exits 0. "The command ran and returned 0" and "we never observed what the command did" are therefore indistinguishable to any caller.

Both exec paths on main share the shape:

// crates/openshell-cli/src/run.rs:1498  — non-interactive sandbox exec
let mut exit_code = 0i32;
// ...
//   :1515  Some(exec_sandbox_event::Payload::Exit(exit)) => { exit_code = exit.exit_code; }
// ...
Ok(exit_code)   // returned whether or not an Exit event ever arrived

The interactive path repeats it at :1931, with the Exit arm at :1948.

exit_code is seeded with the success value and is only ever overwritten by an Exit event, so a stream that closes early, is cancelled, or is truncated yields a successful exit.

Why this looks like a defect rather than a choice

The server already treats the same condition as an error. exec_loop_result maps a missing exit status to:

// crates/openshell-server/src/grpc/sandbox.rs:1792
"exec relay closed before the command reported an exit status",

returned as Status::unavailable. The correct handling of an unobserved outcome is established in this codebase; it simply is not applied to the CLI's own event loop.

Suggested change

Track whether an Exit event was observed, and when the stream ends without one, exit non-zero with a diagnostic on stderr rather than returning Ok(0). That preserves the invariant that exit 0 means an observed exit status of 0.

Downstream context

NVIDIA/NemoClaw#8796: openshell sandbox exec returned status 0 with zero bytes on both stdout and stderr, and the wrapper reported a successful agent turn that never happened. Two common explanations are already ruled out for that report:

  • the caller passed --no-tty, so no PTY negotiation was involved, and
  • the wrapper had already resolved and pinned the owning gateway with -g <gateway> before dispatch.

NemoClaw has since added a host-side guard that treats a zero-exit, zero-byte dispatch as a failure, but that is a workaround for callers; the exit-status contract is owned here. A QA re-test of that report on the next NemoClaw release is queued, and its result can be attached to this issue.

Related transport behaviour: #2668.

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:acceptedA maintainer decided OpenShell should pursue this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions