unslothai/unsloth · error · TimeoutError

deadline reached before {method} {_redact_url(url)}

Error message

deadline reached before {method} {_redact_url(url)}

What it means

Thrown in PlanReview.start (research-activity-panel.tsx:542-565) when a research run sits in awaiting_approval but latest.planHash is falsy after optionally saving plan edits via updateResearchPlan. The approval call (approveResearchRun) must send an opaque hash the backend computed over the plan; without it approval cannot prove the user reviewed that exact plan, so the flow aborts before the API call. Caught locally and shown as 'Could not start research'.

Source

Thrown at scripts/virustotal_scan.py:396

        budget is spent so the caller can degrade to a warning row.

        `max_attempts = 1` disables retrying, which is mandatory for a POST to a
        single-use signed upload URL: replaying it can only ever be rejected.

        `deadline` is checked BEFORE each attempt. One attempt can block for the
        full socket timeout, so a loop that only checks afterwards can overrun the
        caller's budget by minutes and get the whole step killed before it writes
        a summary.
        """
        headers = {"x-apikey": self._api_key, "accept": "application/json"}
        if extra_headers:
            headers.update(extra_headers)

        backoff = self._request_interval if self._request_interval > 0 else 1.0
        last_error = ""
        for attempt in range(1, max_attempts + 1):
            if deadline is not None and self._clock() >= deadline:
                raise TimeoutError(f"deadline reached before {method} {_redact_url(url)}")
            self._throttle(deadline)
            # Re-check: pacing sleeps between the check above and the call below, so
            # without this a request could start after the deadline and then block
            # for the full socket timeout, overrunning the step's own budget.
            if deadline is not None and self._clock() >= deadline:
                raise TimeoutError(
                    f"deadline reached while pacing before {method} {_redact_url(url)}"
                )
            try:
                # Clamp the socket budget to what is left. Without this a call that
                # starts just before the deadline can still block for the full
                # socket timeout and consume the whole cushion the step relies on
                # to write its summary.
                socket_timeout = _SOCKET_TIMEOUT
                if deadline is not None:
                    socket_timeout = max(1.0, min(socket_timeout, deadline - self._clock()))
                status, payload = self._transport(method, url, headers, body, socket_timeout)
            except Exception as error:  # network layer, DNS, TLS, truncated read

View on GitHub (pinned to 203007d190)

Solutions

  1. Refresh the research run (refetch the session snapshot) so the store re-ingests a run with planHash populated.
  2. Update the studio backend to a version that emits plan_hash on awaiting_approval runs.
  3. If it persists, inspect the run object in the Network tab (GET /api/chat/research-runs/{id}) to confirm the backend omits plan_hash, and report it as a backend bug.
  4. Cancel and re-create the research run so a fresh plan (with hash) is generated.
Defensive patterns

Strategy: type-guard

Validate before calling

function canApprovePlan(run: ResearchRun): boolean {
  return run.status === 'awaiting_approval' && typeof run.planHash === 'string' && run.planHash.length > 0;
}

Type guard

function hasPlanHash(run: ResearchRun | undefined): run is ResearchRun & { planHash: string } {
  return typeof run?.planHash === 'string' && run.planHash.length > 0;
}

Try / catch

try {
  await startResearch();
} catch (error) {
  if (error instanceof Error && error.message === 'The research plan is missing its approval hash.') {
    await refetchResearchRun(runId); // re-ingest a fresh snapshot, then retry
  } else throw error;
}

Prevention

When it happens

Trigger: Run status awaiting_approval whose run.planHash is null/undefined — either the snapshot fetched from the backend never carried plan_hash, or updateResearchPlan returned a run object lacking the field (older backend version, or a run created before the hash feature). Editing the draft makes no difference; the check applies to `latest` in both branches.

Common situations: Frontend newer than the backend (backend predates the approval-hash field); stale run snapshot in useResearchRunStore that predates a backend restart/re-creation of the run; backend bug omitting plan_hash on the awaiting_approval transition.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/2f6c722012d4b3cf. Report an issue: GitHub.