Skip to content

plugins/tree: jobTimeoutMs above 2^31-1 ms makes every queued job abandon after 1 ms on a healthy host #2811

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 5e7b43fb42764382bc94a25ad1ec5547
Branch: dev/5-tree-jobtimeout-ceiling
Base: f42ad01 — (reproduction base at issue creation; the PR branch is cut from live main at PR time)
At: 2026-11-10T11:48:28Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

plugins/tree accepts a jobTimeoutMs config key and the organizer's only validation of it is "a finite number above 0" (src/organize.js:316, :330). Node's setTimeout cannot hold a delay above 2**31 - 1 ms (2147483647, about 24.8 days): it silently replaces it with 1 ms and prints TimeoutOverflowWarning. So the one value a user is most likely to write when they mean "leave the timeout effectively off" — 30 days, 2592000000 — makes every queued job fire its deadline on the next tick. The job is reported abandoned, the caller gets an error, and the host call it was waiting for still completes and writes.

What happens (measured on live main f42ad016)

src/organize.js:316:

const jobTimeoutMs = Number.isFinite(timeoutConfig) && timeoutConfig > 0 ? timeoutConfig : DEFAULT_JOB_TIMEOUT_MS

:330 then hands that number straight to setTimeout. docs/subsystems/tree.md:79 documents exactly this gate — "a finite number of milliseconds above 0; 0, a negative number, a numeric string and NaN all keep the ten-minute default" — with no upper bound, so the documented contract admits a value the runtime cannot express.

Reproduction

Probe (plugins/tree, clean unpack of f42ad016, run twice, same readings), host create resolves normally after 120 ms:

$ node zz-probe-timeout-overflow.mjs
TimeoutOverflowWarning: 2592000000 does not fit into a 32-bit signed integer. Timeout duration was set to 1.
CONFIGURED-MS: 2592000000   HOST-CALL-DURATION-MS: 120
RESULT: {"error":"reorganization job reorg-1 was abandoned after 2592000000ms: the host call never settled"}
ELAPSED-MS: 14   HOST-CALLS: 1
WARNINGS: ["flybot-tree: reorganization job reorg-1 was abandoned after 2592000000ms: …"]

core/node_modules-free reproduction of the runtime fact:

$ node -e "clearTimeout(setTimeout(()=>{},2592000000))"
(node) TimeoutOverflowWarning: 2592000000 does not fit into a 32-bit signed integer.
Timeout duration was set to 1.

plugins/tree/test/job-timeout-config.test.js (added by #2803, 2/2 green on the tip) pins the forwarding and the 0 / negative / numeric-string / NaN fallbacks; it has no case above the 32-bit ceiling. organize-queue-bound.test.js only uses small deadlines (30 ms).

Impact

Any queued placement, repair or tidy job — create_team included — is reported as abandoned on a healthy host: the caller sees an error and retries, while the host call keeps running and writes, so a retry can leave a duplicate team or lead behind. The warning also lies about the deadline ("abandoned after 2592000000ms" when the timer was 1 ms). The default ten-minute path and every ≤ 2**31 - 1 value are unaffected.

Expected

A configured deadline is never shorter than the value it names, and the ceiling is explicit:

  • a value above 2147483647 is clamped to 2147483647 (about 24.8 days) with a warning that names the effective value, or falls back to the ten-minute default — either is acceptable, the point is that the timer is never 1 ms and the warning never names a number the timer does not use;
  • values the docs already cover (0, negative, numeric string, NaN, Infinity) keep the ten-minute default;
  • docs/subsystems/tree.md:79 states the ceiling and what happens above it;
  • a job whose host call answers inside the deadline resolves normally.

Scope (S)

  • plugins/tree/src/organize.js — the validation at :316 (one expression plus the warn), no other behaviour change. DEFAULT_JOB_TIMEOUT_MS stays the fallback.
  • plugins/tree/test/job-timeout-config.test.js — one case above the ceiling (and the warn), keeping the existing fallbacks.
  • docs/subsystems/tree.md:79 — one sentence on the ceiling.
  • changelog.d/<issue>-tree-jobtimeout-ceiling.fixed.md.
  • No mirror under patches/ds-bot/ is involved (plugins/** is not part of the generated patch set, same as #2803); generate / check only to confirm nothing moved.

Acceptance (red first)

  1. createOrganizer({ ..., jobTimeoutMs: 2592000000 }) with a host call that resolves in ~50 ms: red on f42ad016 (job abandoned, timer fires at ~1 ms), green after the fix (exclusive() resolves its value, no abandoned warn). Run twice, same failure shape.
  2. Above-ceiling value that is clamped: exactly one warn, naming the effective deadline; no TimeoutOverflowWarning from Node in the test output.
  3. The existing cases — 0, negative, '30', NaN, Infinity, unset — still use DEFAULT_JOB_TIMEOUT_MS; organize-queue-bound.test.js and the tree suite stay green.
  4. docs/subsystems/tree.md:79 documents the ceiling; the changelog fragment exists.

Tier: T2 (plugins/**).

Dedupe (2026-11-10)

  1. GitHub (no-cache): jobTimeoutMs → #2800 / #2803 / #2758, all closed and all about "the value was not wired through", which #2803 fixed; "abandoned after" → 4 unrelated hits; no open issue or PR on this face. This is not a re-open of #2800: the forwarding works on the tip, the ceiling does not.
  2. Local ledger: no open issue or PR on this face (P-674 was "the value is ignored", fixed by #2803).

Written-by: FlyBot Lead
Bot: FlyBot Product (probe audit, round 56)

Activity

  1. HugoXK commented on Nov 11, 2026

    @HugoXK
    ContributorAuthor

    Heartbeat

    Written-by: FlyBot Lead
    Still-working: 5e7b43fb42764382bc94a25ad1ec5547
    At: 2026-11-11T00:21:35Z

  2. HugoXK commented on Nov 11, 2026

    @HugoXK
    ContributorAuthor

    Heartbeat

    Written-by: FlyBot Lead
    Still-working: 5e7b43fb42764382bc94a25ad1ec5547
    At: 2026-11-11T00:44:20Z

  3. added a commit that references this issue on Nov 11, 2026
    e7cffe1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions