{"record":{"id":"7d5579a058a57bac","repo":"paperclipai/paperclip","slug":"retry-was-skipped-7d5579","errorCode":null,"errorMessage":"Retry was skipped.","messagePattern":"Retry was skipped\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ui/src/pages/LegacyInbox.tsx","lineNumber":1685,"sourceCode":"  const [retryingRunIds, setRetryingRunIds] = useState<Set<string>>(new Set());\n\n  const retryRunMutation = useMutation({\n    mutationFn: async (run: HeartbeatRun) => {\n      const payload: Record<string, unknown> = {};\n      const context = run.contextSnapshot as Record<string, unknown> | null;\n      if (context) {\n        if (typeof context.issueId === \"string\" && context.issueId) payload.issueId = context.issueId;\n        if (typeof context.taskId === \"string\" && context.taskId) payload.taskId = context.taskId;\n        if (typeof context.taskKey === \"string\" && context.taskKey) payload.taskKey = context.taskKey;\n      }\n      const result = await agentsApi.wakeup(run.agentId, {\n        source: \"on_demand\",\n        triggerDetail: \"manual\",\n        reason: \"retry_failed_run\",\n        payload,\n      });\n      if (!(\"id\" in result)) {\n        throw new Error(result.message ?? \"Retry was skipped.\");\n      }\n      return { newRun: result, originalRun: run };\n    },\n    onMutate: (run) => {\n      setRetryingRunIds((prev) => new Set(prev).add(run.id));\n    },\n    onSuccess: ({ newRun, originalRun }) => {\n      queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(originalRun.companyId) });\n      queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(originalRun.companyId, originalRun.agentId) });\n      navigate(`/agents/${originalRun.agentId}/runs/${newRun.id}`);\n    },\n    onSettled: (_data, _error, run) => {\n      if (!run) return;\n      setRetryingRunIds((prev) => {\n        const next = new Set(prev);\n        next.delete(run.id);\n        return next;\n      });","sourceCodeStart":1667,"sourceCodeEnd":1703,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/ui/src/pages/LegacyInbox.tsx#L1667-L1703","documentation":"In the LegacyInbox retry flow, agentsApi.wakeup with reason \"retry_failed_run\" must return a new run object containing \"id\". If the response lacks an id, the server skipped the retry and returned only a message; this error is thrown with the server message as fallback. The mutation's onSuccess navigates to the new run, so a skipped retry cannot proceed.","triggerScenarios":"Invoking the inbox retry mutation on a run and receiving a wakeup response without \"id\" — the backend declined to spawn the retry (agent paused, budget/concurrency gates, run state changed) and returned a message-only payload.","commonSituations":"Retrying from a stale inbox list after the run was already retried or the agent was paused; on_demand wakeups rejected by server guard rails; the original run was deleted concurrently.","solutions":["Read result.message for the skip reason and address it (unpause agent, free budget).","Refresh the inbox/heartbeats data and retry if the click used stale state.","Confirm the run still exists and is still in a retriable failed state.","Check server logs for the wakeup skip branch for this reason code."],"exampleFix":"// before\nif (!(\"id\" in result)) {\n  throw new Error(result.message ?? \"Retry was skipped.\");\n}\n// after\nif (!(\"id\" in result)) {\n  setRetryingRunIds((prev) => { const n = new Set(prev); n.delete(run.id); return n; });\n  toast.error(result.message ?? \"Retry was skipped.\");\n  throw new Error(result.message ?? \"Retry was skipped.\");\n}","handlingStrategy":"type-guard","validationCode":"// refresh inbox/run state before enabling retry\nawait queryClient.invalidateQueries({ queryKey: [\"heartbeats\"] });","typeGuard":"const isNewRun = (r: unknown): r is { id: string } =>\n  typeof r === \"object\" && r !== null && \"id\" in r && typeof (r as { id: unknown }).id === \"string\";","tryCatchPattern":"try {\n  const { newRun } = await retryMutation.mutateAsync(run);\n  navigate(`/agents/${newRun.agentId}/runs/${newRun.id}`);\n} catch (e) {\n  toast.error(e.message);\n} finally {\n  setRetryingRunIds((prev) => { const n = new Set(prev); n.delete(run.id); return n; });\n}","preventionTips":["Track retrying run ids and disable the Retry button while in flight.","Refresh inbox data before retrying so clicks act on current run state.","Check agent pause/budget gates and surface them next to the Retry action."],"tags":["ui","api-response","retry","inbox"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}