bytedance/deer-flow · warning

Failed to fetch subtask steps: ${res.status}

Error message

Failed to fetch subtask steps: ${res.status}

What it means

Thrown when a paginated GET of thread events filtered to event_types=subagent.step&task_id=... fails. The loop pages through the event stream (limit=pageSize, after_seq cursor) up to SUBTASK_STEPS_MAX_PAGES to reconstruct subtask steps client-side. Any non-2xx page aborts the whole fetch with only the numeric status in the message.

Source

Thrown at frontend/src/core/tasks/api.ts:49

    threadId,
  )}/runs/${encodeURIComponent(runId)}/events`;

  const events: FetchedEvent[] = [];
  let afterSeq: number | undefined;

  for (let page = 0; page < SUBTASK_STEPS_MAX_PAGES; page++) {
    const params = new URLSearchParams({
      event_types: "subagent.step",
      task_id: taskId,
      limit: String(pageSize),
    });
    if (afterSeq !== undefined) {
      params.set("after_seq", String(afterSeq));
    }

    const res = await fetch(`${base}?${params.toString()}`);
    if (!res.ok) {
      throw new Error(`Failed to fetch subtask steps: ${res.status}`);
    }
    const batch = (await res.json()) as FetchedEvent[];
    events.push(...batch);

    if (batch.length < pageSize) {
      break;
    }
    const lastSeq = batch[batch.length - 1]?.seq;
    if (lastSeq === undefined) {
      break; // can't advance the cursor; stop rather than refetch page 0 forever
    }
    afterSeq = lastSeq;
  }

  return eventsToSteps(events, taskId);
}

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. On 404/403, stop showing the subtask panel for that task instead of erroring the whole view
  2. Confirm the thread exists via GET /api/threads/{id} before opening subtasks
  3. Check the raw URL in devtools to verify event_types/task_id/after_seq query encoding
  4. If 500s correlate with load, retry the single failed page with the same cursor after a short delay

Example fix

// before
const res = await fetch(`${base}?${params.toString()}`);
if (!res.ok) throw new Error(`Failed to fetch subtask steps: ${res.status}`);

// after
const res = await fetch(`${base}?${params.toString()}`);
if (!res.ok) {
  if (res.status === 404 || res.status === 403) return []; // task gone / no access
  throw new Error(`Failed to fetch subtask steps: ${res.status}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

function validEventQuery(taskId: string, afterSeq?: number): boolean {
  return taskId.length > 0 && (afterSeq === undefined || Number.isSafeInteger(afterSeq) && afterSeq >= 0);
}

Type guard

export function isSubtaskStepsError(e: unknown): e is Error {
  return e instanceof Error && e.message.startsWith('Failed to fetch subtask steps:');
}

Try / catch

try {
  return await fetchSubtaskSteps(taskId);
} catch (e) {
  if (isSubtaskStepsError(e)) {
    const status = Number(e.message.split(': ')[1]);
    if (status === 404 || status === 403) return []; // gone / no access
    return retryOnce(() => fetchSubtaskSteps(taskId));
  }
  throw e;
}

Prevention

When it happens

Trigger: Fetching steps for a deleted or expired thread (404); unauthenticated access to another user's thread events (403); events endpoint returning 500 during heavy event writes; after_seq pointing past retention boundaries.

Common situations: Subtask panel loading an old thread whose events were pruned by retention; session expiry mid-pagination; backend version where the event-types filter param changed.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/82d98642f6c9174d. Report an issue: GitHub.