paperclipai/paperclip · error · GitHubWebhookRecoveryError

github_webhook_recovery_http

github_webhook_recovery_http

Error message

github_webhook_recovery_http

What it means

Thrown when GitHub returned a status code different from the expected one (200 by default, 202 for redelivery, 201 for installation token mint, 204 for token revoke). The error carries the HTTP status, an optional retryAfterMs parsed from Retry-After / x-ratelimit headers, and requestMayHaveBeenAccepted=true only for 5xx/408 on uncertain mutations.

Source

Thrown at server/src/services/chat-github-webhook-config.ts:364

              authorization: `Bearer ${input.token}`,
              "x-github-api-version": "2022-11-28",
              ...(input.body ? { "content-type": "application/json" } : {}),
            },
            ...(input.body ? { body: input.body } : {}),
          },
        );
        if (controller.signal.aborted) {
          await response.body?.cancel().catch(() => undefined);
          throw new GitHubWebhookRecoveryError(
            "github_webhook_recovery_transport",
            null,
            null,
            input.uncertainMutation === true,
          );
        }
        if (response.status !== (input.expectedStatus ?? 200)) {
          await response.body?.cancel().catch(() => undefined);
          throw new GitHubWebhookRecoveryError(
            "github_webhook_recovery_http",
            response.status,
            retryDelay(response.headers),
            input.uncertainMutation === true &&
              (response.status >= 500 || response.status === 408),
          );
        }
        return input.project(response, controller.signal);
      })(),
    ]);
  } catch (error) {
    if (error instanceof GitHubWebhookRecoveryError) throw error;
    throw new GitHubWebhookRecoveryError(
      "github_webhook_recovery_transport",
      null,
      null,
      input.uncertainMutation === true,
    );

View on GitHub (pinned to 01ad858492)

Solutions

  1. Read e.statusCode and e.retryAfterMs: wait retryAfterMs before retrying on 429/403-with-ratelimit headers.
  2. On 401, mint a fresh App JWT — yours likely expired (60s–10min TTL); on 403 check App permissions and installation status.
  3. On 404, verify the delivery/installation still exists before retrying.
  4. On 5xx/408 during a redelivery, reconcile first (getGitHubAppWebhookDelivery) because the mutation may have been accepted.
  5. Back off and retry GETs with jitter; stop hammering if 429 persists.

Example fix

// before
await listGitHubAppWebhookDeliveries({ fetch, appToken }); // throws on 429
// after
try {
  return await listGitHubAppWebhookDeliveries({ fetch, appToken });
} catch (e) {
  if (e instanceof GitHubWebhookRecoveryError && e.code === "github_webhook_recovery_http" && e.statusCode === 429 && e.retryAfterMs) {
    await sleep(e.retryAfterMs);
    return await listGitHubAppWebhookDeliveries({ fetch, appToken });
  }
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

function isHttpRecoveryError(e: unknown): e is GitHubWebhookRecoveryError & { statusCode: number; retryAfterMs: number | null } {
  return e instanceof GitHubWebhookRecoveryError && e.code === "github_webhook_recovery_http";
}

Try / catch

try {
  await listGitHubAppWebhookDeliveries({ fetch, appToken });
} catch (e) {
  if (e instanceof GitHubWebhookRecoveryError && e.code === "github_webhook_recovery_http") {
    if (e.retryAfterMs) await sleep(e.retryAfterMs); // rate-limited: respect Retry-After / x-ratelimit-reset
    else if (e.statusCode !== null && e.statusCode >= 500) await sleep(jitteredBackoff());
    else throw e; // 401/403/404/422 need action, not retry
  } else throw e;
}

Prevention

When it happens

Trigger: 401/403 from an expired or wrong-scope App JWT; 404 from a deleted delivery or installation; 422 from a bad repository list in the token request; 429 rate limit (retryAfterMs set from x-ratelimit-reset); 5xx GitHub server errors; 408 request timeout; redelivery POST returning anything but 202.

Common situations: App JWT expired (JWTs live ~10 minutes); installation uninstalled; App lacking webhook read permissions; hitting the 15,000 req/hr App rate limit in recovery loops; GitHub incident causing 5xx.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/12a6379299582e53. Report an issue: GitHub.