Skip to content

fix(mcp): make execute timeout opt-in for unprivileged commands (#54) - #56

Merged
cuihtlauac merged 1 commit into
mainfrom
fix/54-unprivileged-timeout-opt-in
Oct 2, 2026
Merged

cuihtlauac merged 1 commit into
mainfrom
fix/54-unprivileged-timeout-opt-in

Conversation

@cuihtlauac

Copy link
Copy Markdown
Member

Closes #54. Stacked on #55 (base = feat/g7-unprivileged-invariant); review/merge after #55.

Problem

execute applied its wall-clock timeout (default 120s) uniformly to privileged and unprivileged requests. An unprivileged command runs as the user with no escalation, but the deadline also counts human-approval latency — so a command that was approved and executed could still be returned to the caller as "server did not respond within 120s". A caller that retries a "timed-out" side-effecting command then double-executes it.

Fix

The client-side read timeout becomes opt-in for unprivileged requests (read_timeout_for): honored when the caller sets timeout, otherwise the read waits as long as the daemon needs. This is safe because the daemon always answers or drops:

  • approval is bounded by the 60s default-deny prompt;
  • execution is bounded by the executor's own exec timeout;
  • connect/write keep their 5s PHASE_TIMEOUT, and a dead daemon surfaces as EOF — an unbounded read cannot hang on a daemon that has died.

Privileged requests keep the 120s default; an explicit timeout is always honored and clamped to 600000.

Scope note

This fixes the observed failure (the client-side "did not respond" false-failure + the double-execution risk). The daemon-side exec bound (SUDO_PROXY_EXEC_TIMEOUT_SECS, 300s) is intentionally left as a resource guard (assurance-case G6.4) — long-running unprivileged work should background itself, as the issue's own example does (setsid … & disown). Because #55 already refuses local unprivileged execute(), this governs the remaining remote unprivileged path.

Tests

read_timeout_for unit test (opt-in/kept/honored+clamped); full suite green (minus the pre-existing flaky burst test); clippy -D warnings clean. Tool description and docs/mcp.md updated.

🤖 Generated with Claude Code

)

`execute` applied its wall-clock `timeout` (default 120s) uniformly to
privileged and unprivileged requests. For an unprivileged command — which
runs as the user with no escalation — the deadline also counts human-
approval latency, so a command that was approved and ran could still be
reported to the caller as "server did not respond within 120s". A caller
that then retries a side-effecting command double-executes it.

Make the timeout opt-in for unprivileged requests: honored when the caller
sets it, otherwise the client read waits as long as the daemon needs. The
daemon still bounds the command (the 60s default-deny approval prompt and
the executor's own exec timeout), the connect/write phases keep their 5s
PHASE_TIMEOUT, and a dead daemon surfaces as EOF — so an unbounded read
cannot hang on a daemon that has died. Privileged requests keep the 120s
default; an explicit `timeout` is always honored and clamped.

Stacked on #55, which already refuses *local* unprivileged execute(), so
this governs the remaining (remote) unprivileged path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cuihtlauac
cuihtlauac changed the base branch from feat/g7-unprivileged-invariant to main October 2, 2026 06:56
@cuihtlauac
cuihtlauac merged commit a057d39 into main Oct 2, 2026
16 checks passed
@cuihtlauac
cuihtlauac deleted the fix/54-unprivileged-timeout-opt-in branch October 2, 2026 06:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

execute: unprivileged commands should not be subject to the approval/execution timeout

1 participant