Skip to content

mcpx(daemon): long-poll waitMs is uncapped and overflows — a client can pin a handler past the operator's wait settings, and a ~317-year wait returns instantly #2231

Closed

Description

@dezren39

mcpx(daemon): long-poll waitMs is uncapped and overflows — a client can pin a handler past the operator's wait settings, and a ~317-year wait returns instantly

Summary

Both mcpx daemon long-poll handlers take the client's waitMs query parameter verbatim, with no upper bound, and convert it with time.Duration(ms) * time.Millisecond, which overflows for large values:

  • pkgs/mcpx/internal/daemon/ops.go:476-480 — handleTaskResult (GET /v1/tasks/{id}/result): the operator's tasks.resultWait setting (default 2m, pkgs/mcpx/internal/defaults/defaults.json:83; documented in docs/configuration.md as "how long collecting a task result blocks before giving up") is replaced by any positive waitMs the client sends.
  • pkgs/mcpx/internal/daemon/routes_proto.go:514-518 — handleAskPoll (GET /v1/ask/{id}): the same pattern replaces the proto.askPoll default (500ms).

The daemon's HTTP server sets only ReadHeaderTimeout (pkgs/mcpx/internal/daemon/server.go startHTTP), so nothing else bounds how long these handlers hold a goroutine and a connection.

Verified behavior (laptop probe, this run)

Probe: ~/git/fleet/probe-2026-10-29-laneA-waitms/ (laptop worktree at main 17819113; both handler lines verified byte-identical at current main 24901e7b). The probe drives the real handleTaskResult / handleAskPoll with a never-finishing task:

Request Result
task result waitMs=300 408 after 300.2ms (control: small waits are honoured)
task result waitMs=3600000 (1h = 30× the operator's 2m default) still blocked after 2s; returned 200 only when the task was released — accepted, no cap
task result waitMs=10000000000000 (1e13 ms ≈ 317 years) 408 in 13µs — time.Duration(ms) * time.Millisecond overflows int64 (threshold ≈ 9.22e12 ms) and wraps negative, so context.WithTimeout is already expired
ask poll waitMs=300 200 after 300.6ms (control)
ask poll waitMs=10000000000000 200 in 19.6µs — same overflow, instant return, no error

Legitimate in-tree clients only ever pass small waits (1ms polls in internal/mcpserver/ask.go, the 250ms TaskEager window, daemonAsker.Poll in internal/cli/serve_ask.go:205), so a cap at (or near) the configured defaults breaks nothing in-tree.

Why it matters

  • Operator settings are silently overridden. tasks.resultWait exists so the operator decides how long result collection may block; any local API client can currently multiply it without limit, pinning a handler goroutine + connection per request for hours.
  • The overflow is a silent wrong answer, not an error. A wait past ~292 years (ms > ~9.22e12) wraps to a negative or arbitrary duration: the "wait" returns instantly (task result: a spurious 408 "has not finished yet"), indistinguishable from a genuine timeout. Nothing is logged and no 400 is returned.
  • The same two-line pattern in both handlers means one ruling fixes both.

Acceptance criteria

  • Both handlers clamp or reject an out-of-range waitMs under one pinned rule (triage pins: clamp to the configured default/ceiling vs 400). The operator's tasks.resultWait / a named ask-poll ceiling is the maximum a client can obtain.
  • No waitMs value can produce a negative or wrapped time.Duration: values above the overflow threshold (~9.22e12 ms) follow the same pinned rule as other out-of-range values, never an instant spurious timeout.
  • Regression tests at the handler level: small waitMs honoured (as today); waitMs above the ceiling behaves per the pinned rule; waitMs=10000000000000 does not return an instant 408/200.
  • In-tree client behavior unchanged: 1ms–500ms polls and the tasks.resultWait default path work exactly as before.

Dedup

Searched open + closed issues for waitMs, "task result wait", "long poll wait cap": no hit covers these handlers. Related but distinct: #1679 is the /v1/events since resume cursor (different endpoint, different parameter); #1864 covers call-scoped timeouts for MCP tool calls, not these HTTP long-polls.

Activity

  1. developing-today commented on Oct 29, 2026

    @developing-today
    Contributor
  2. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 29, 2026
  3. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  4. added
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 31, 2026
  5. 14 remaining items

  6. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  7. added
    queue/claimedQueue worker lifecycle state
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    queue/claimedQueue worker lifecycle state
    on Oct 31, 2026
  8. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Lane D resumed task 4314 and re-parked it for 20m: PR 3083 head 155d659 unchanged, Codex review completed with no findings (0 unresolved threads), CI partially green (mcpx-docs build + lint SUCCESS) but plugin tests still QUEUED and stress IN_PROGRESS on run 41208623592 — fleet CI congestion. Resume: all checks green -> merge PR 3083 -> close #2231 -> complete fixed_merged.

  9. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

    • Agent: queue worker (repo-worker-code), lane C shift
    • Session / ID: queue task 4314 (resume)
    • Found via: queue claim
    • Task: Done. PR fix(mcpx): bound daemon long-poll waits #3083 merged (ab529a2): waitMs clamped to the operator ceiling with 400 on overflow/negative for both daemon long-poll handlers. Verified fresh this shift: all substantive CI green on 155d659 (basic lint, plugin tests, stress PR-changed tests, mcpx-docs build; only the fleet-backlog "final: CI time budget" meta job still queued), Codex review completed with no findings, PR mergeable. This issue auto-closed on merge.
    • Expectation: met.
    • Links: PR fix(mcpx): bound daemon long-poll waits #3083
  10. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions