What happens
job_run chooses between waiting for a job and detaching from it by comparing the
job's own deadline against the turn's clock (packages/core/src/job-server.ts:324):
if (jobDeadlineMs(job) > turnTimeoutMs) { /* start, don't wait */ }
The comment directly above states the intent — the choice comes from "the job's own
declaration against the turn's own clock … not a field in this call, and not a field
in the manifest that could disagree with either number."
There is a third clock, and it is not in that comparison. The harness hosting the
MCP client applies its own per-tool-call timeout. The toolkit neither sets nor reads
it: MCP_TOOL_TIMEOUT and MCP_TIMEOUT appear nowhere in this repo. Whenever that
bound is shorter than the job's real duration, it is the one that binds, and the
comparison above is made against a number that never applies.
Observed
A job declaring wallClockMs: 150000 + deadlineHeadroomMs: 45000 against
limits.turnTimeoutMs: 300000 — so 195000 <= 300000 and job_run waited, correctly
by its own arithmetic. The body's real duration was ~91s.
turn_start …
turn_done … sent=1 ms=87760 ← the turn closed here
…job posts its own result through its report channel…
tool_call tool="mcp__jobs__job_run" outcome=ok ms=90834 ← logged after that
The harness abandoned the call at ~87.8s. The model, handed a failing tool, composed a
message telling humans the job had timed out and produced nothing — and named two of
them to go look. Three seconds later the job finished and posted its own, correct
result. A human saw both messages.
The logging half, which is the worse half
There is no timeout, abort, or error line anywhere in the toolkit's log for that run.
outcome=ok ms=90834 is the only record and it reads as a clean success. The toolkit
cannot currently tell that a call it served was abandoned, so the failure is invisible
to it and to anyone reading its logs afterwards. The only artifact is a wrong sentence
in a channel, while every log the toolkit keeps says the run was fine.
Suggestions
Roughly in order of how much each helps:
- Make the two clocks unable to disagree, as the existing comment already intends:
set the harness's tool-call timeout explicitly from limits.turnTimeoutMs when
spawning it, so a job that fits inside a turn also fits inside the transport.
- Failing that, fold the harness bound into the comparison at
job-server.ts:324, so
a job near the edge detaches rather than waits.
- Independently of both: log when a waited call completes after its turn has ended.
Even with (1) this is the signal that says the clocks have drifted apart again, and
it is what turns this from "a wrong message appeared in a channel" into something
greppable.
(3) stands on its own merits — a job host that cannot observe an abandoned call has no
way to report this class of failure at all.
What happens
job_runchooses between waiting for a job and detaching from it by comparing thejob's own deadline against the turn's clock (
packages/core/src/job-server.ts:324):The comment directly above states the intent — the choice comes from "the job's own
declaration against the turn's own clock … not a field in this call, and not a field
in the manifest that could disagree with either number."
There is a third clock, and it is not in that comparison. The harness hosting the
MCP client applies its own per-tool-call timeout. The toolkit neither sets nor reads
it:
MCP_TOOL_TIMEOUTandMCP_TIMEOUTappear nowhere in this repo. Whenever thatbound is shorter than the job's real duration, it is the one that binds, and the
comparison above is made against a number that never applies.
Observed
A job declaring
wallClockMs: 150000+deadlineHeadroomMs: 45000againstlimits.turnTimeoutMs: 300000— so195000 <= 300000andjob_runwaited, correctlyby its own arithmetic. The body's real duration was ~91s.
The harness abandoned the call at ~87.8s. The model, handed a failing tool, composed a
message telling humans the job had timed out and produced nothing — and named two of
them to go look. Three seconds later the job finished and posted its own, correct
result. A human saw both messages.
The logging half, which is the worse half
There is no timeout, abort, or error line anywhere in the toolkit's log for that run.
outcome=ok ms=90834is the only record and it reads as a clean success. The toolkitcannot currently tell that a call it served was abandoned, so the failure is invisible
to it and to anyone reading its logs afterwards. The only artifact is a wrong sentence
in a channel, while every log the toolkit keeps says the run was fine.
Suggestions
Roughly in order of how much each helps:
set the harness's tool-call timeout explicitly from
limits.turnTimeoutMswhenspawning it, so a job that fits inside a turn also fits inside the transport.
job-server.ts:324, soa job near the edge detaches rather than waits.
Even with (1) this is the signal that says the clocks have drifted apart again, and
it is what turns this from "a wrong message appeared in a channel" into something
greppable.
(3) stands on its own merits — a job host that cannot observe an abandoned call has no
way to report this class of failure at all.