Gate autonomous task runs on the execution approval

The manual runTask path enforces assertExecutionApproval, but the
autonomous heartbeat path (enqueueHeartbeatRuns -> enqueueRun -> the
queue worker) did not, so a high-risk or filesystem task whose approval
was still pending — the normal state in ai_cofounder mode, or after an
operator declines — was executed unsupervised.

Apply the same gate in enqueueRun. Pending/declined approvals leave the
task unqueued (the heartbeat's existing try/catch skips it) and it is
picked up on the next tick once approved. ai_ceo mode auto-approves at
task creation, so autonomous companies are unaffected.
This commit is contained in:
ZacharyZhang-NY committed 2026-06-26 17:34:09 -04:00
1 parent c8de72c810
commit fd52dc0861
2 files changed
+69

No files matched your search

@@ -307,6 +307,11 @@ export class TaskExecutionService {
private enqueueRun(orgId: string, taskId: string): string {
const task = this.requireTask(orgId, taskId);
// Autonomous execution honours the same gate as a manual run: a high-risk or
// filesystem task whose approval is still pending (or was declined) is left
// unqueued and retried once the operator approves it, instead of running
// unsupervised. enqueueHeartbeatRuns swallows the throw and moves on.
this.assertExecutionApproval(orgId, task);
const agent = this.requireAssignedAgent(orgId, task);
const provider = this.requireAgentProvider(orgId, agent);
const workspace = this.workspace(task.workspace_id);