Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 17 additions & 20 deletions packages/core/cron/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ const CRON_PREPARE_TIMEOUT_MS = parsePositiveIntEnv(
2 * 60_000
);

/**
* Hard upper bound for the actual agent turn (`agent.sendMessage`). OpenCode
* sessions can wedge waiting on approvals or remote provider calls; we bound
* the run so a stuck turn doesn't permanently lock out the job.
*/
const CRON_AGENT_TIMEOUT_MS = parsePositiveIntEnv(
process.env.ODE_CRON_AGENT_TIMEOUT_MS,
2 * 60 * 60_000
);
// NOTE: There is intentionally no hard upper bound on the agent turn itself
// (`agent.sendMessage`) anymore. Cron jobs that drive long agent workflows
// (Sentry triage, board grooming, multi-PR sweeps) routinely exceeded the
// previous 2h bound and surfaced as `Request timed out` failures even though
// the underlying agent was making progress. We rely on:
// * the prepare-step timeout above to guarantee the in-process
// `runningJobIds` lock can't be wedged by a hung session/worktree setup;
// * the agent adapter's own per-request handling for genuinely stuck turns.
// If a turn really hangs forever, the daemon restart path
// (`reconcileInterruptedCronJobs`) will still surface and retry it.

function parsePositiveIntEnv(raw: string | undefined, fallback: number): number {
if (!raw) return fallback;
Expand Down Expand Up @@ -336,17 +337,13 @@ async function runCronJob(job: CronJobRecord, minuteStartMs: number): Promise<vo
});
}

const responses = await withTimeout(
agent.sendMessage(
job.channelId,
sessionId,
job.messageText,
cwd,
options,
buildCronAgentContext(job, runId)
),
CRON_AGENT_TIMEOUT_MS,
"Cron agent turn"
const responses = await agent.sendMessage(
job.channelId,
sessionId,
job.messageText,
cwd,
options,
buildCronAgentContext(job, runId)
);
const finalText = buildFinalResponseText(responses) ?? "_Done_";

Expand Down
38 changes: 15 additions & 23 deletions packages/core/tasks/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,14 @@ const TASK_PREPARE_TIMEOUT_MS = parsePositiveIntEnv(
2 * 60_000,
);

/**
* Hard upper bound for the actual agent turn (`agent.sendMessage`). OpenCode
* sessions can wedge waiting on approvals or remote provider calls; we bound
* the run so a stuck turn doesn't permanently lock out the task row. Matches
* the cron default (2h) — tasks tend to be heavier since they often do
* scripted long-running work, but anything longer than 2h should really be
* split into multiple scheduled runs.
*/
const TASK_AGENT_TIMEOUT_MS = parsePositiveIntEnv(
process.env.ODE_TASK_AGENT_TIMEOUT_MS,
2 * 60 * 60_000,
);
// NOTE: There is intentionally no hard upper bound on the agent turn itself
// (`agent.sendMessage`) anymore. Tasks often drive long-running scripted
// work (deploys, batch fixes, scheduled audits) that legitimately runs for
// hours, and the previous 2h cap was surfacing as `Request timed out` even
// when the agent was making progress. Hung sessions are still bounded by:
// * the prepare-step timeout above (covers session/worktree setup);
// * the agent adapter's own per-request handling;
// * `reconcileInterruptedTasks` on daemon restart.

function parsePositiveIntEnv(raw: string | undefined, fallback: number): number {
if (!raw) return fallback;
Expand Down Expand Up @@ -415,17 +411,13 @@ async function runTask(task: TaskRecord): Promise<void> {
});
}

const responses = await withTimeout(
agent.sendMessage(
task.channelId,
sessionId,
task.messageText,
cwd,
options,
buildTaskAgentContext(task),
),
TASK_AGENT_TIMEOUT_MS,
"Task agent turn",
const responses = await agent.sendMessage(
task.channelId,
sessionId,
task.messageText,
cwd,
options,
buildTaskAgentContext(task),
);
const finalText = buildFinalResponseText(responses) ?? "_Done_";

Expand Down
Loading