paperclipai/paperclip · warning · Error
Retry was skipped.
Error message
Retry was skipped.
What it means
Fallback thrown by retryFailedRun when the retry API response has neither "id", "actionId", nor a "message" field — an unexpected/empty response shape from the server. "Retry was skipped." signals the server declined the retry without a structured result.
Solutions
- Check result.message first — the code already prefers it; surface it to the user instead of the generic fallback.
- Verify the server's retry endpoint response shape matches the shared API contract (packages/shared types).
- Confirm the agent/run supports retrying (check run status server-side) before calling retry.
- Log the raw response body to diagnose the unexpected shape.
Example fix
// before
throw new Error(result.message ?? "Retry was skipped.");
// after
if (!result.message) console.error("unexpected retry response", result);
throw new Error(result.message ?? `Retry was skipped (status: ${result.status ?? "unknown"}).`); Defensive patterns
Strategy: type-guard
Validate before calling
// validate server contract in tests expect(retryResponseSchema.safeParse(result).success).toBe(true); // id | actionId | message required
Type guard
function isRetryResult(r: unknown): r is { id?: string; actionId?: string; runId?: string; issueId?: string; message?: string } {
return typeof r === "object" && r !== null && ("id" in r || "actionId" in r || "message" in r);
} Try / catch
const result = await retryResponse.json();
if (!isRetryResult(result)) {
console.error("unexpected retry response", result);
throw new Error("Retry was skipped: unrecognized server response.");
} Prevention
- Keep ui API clients and shared validators in sync with the server retry endpoint contract.
- Log raw response bodies for any retry response missing id/actionId to catch server drift fast.
- Pin server and UI versions together during upgrades to avoid response-shape mismatch.
When it happens
Trigger: POST {agentPath}/retry returns a body lacking id/actionId — e.g. server responds with {status:"skipped"} or an error body the client does not recognize, or the endpoint shape drifted from the shared API contract.
Common situations: Server/client version mismatch after an upgrade; server returned an error envelope the client didn't anticipate; retry attempted on an agent type that doesn't support run retrying.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- Retry was skipped.
- Retry was skipped.
- Retry was skipped.
- This retry could not start. Open the task to review its…
- [adapter-ui-loader] Failed to load UI parser for
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/51b76141c7070b9f.
Report an issue: GitHub.
Appendix: source
Thrown at ui/src/api/agents.ts:271
) => {
const result = await api.post<
AgentWakeupResponse | ChatFailedRunRetryResponse
>(agentPath(id, companyId, "/wakeup"), {
source: "on_demand",
triggerDetail: "manual",
reason: "retry_failed_run",
failedRunId,
});
if ("id" in result) return { runId: result.id, issueId: null };
if ("actionId" in result) {
if (result.status === "failed" || result.status === "cancelled") {
throw new Error(
"This retry could not start. Open the task to review its current access and recovery state.",
);
}
return { runId: result.runId, issueId: result.issueId };
}
throw new Error(result.message ?? "Retry was skipped.");
},
loginWithClaude: (id: string, companyId?: string) =>
api.post<ClaudeLoginResult>(agentPath(id, companyId, "/claude-login"), {}),
startAdapterAuthLogin: (
companyId: string,
type: string,
data: { environmentId: string; ttlSeconds?: number; aiConnection?: import("@paperclipai/shared").AiConnectionLoginIntent },
) =>
api.post<AdapterAuthSessionResponse>(
`/companies/${encodeURIComponent(companyId)}/adapters/${encodeURIComponent(type)}/login-sessions`,
data,
),
// The owner response repeats the live prompt on every read while the
// session holds an active public status, so this polling call carries the
// same no-store request option as the active-session read below.
getAdapterAuthLoginStatus: (companyId: string, type: string, sessionId: string) =>
api.get<AdapterAuthSessionOwnerResponse>(
`/companies/${encodeURIComponent(companyId)}/adapters/${encodeURIComponent(type)}/login-sessions/${encodeURIComponent(sessionId)}`,View on GitHub (pinned to 3f1d897a7c)