{"record":{"id":"c45e157059e50b35","repo":"paperclipai/paperclip","slug":"github-webhook-recovery-invalid-response","errorCode":"github_webhook_recovery_invalid_response","errorMessage":"github_webhook_recovery_invalid_response","messagePattern":"github_webhook_recovery_invalid_response","errorType":"error_code","errorClass":"GitHubWebhookRecoveryError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-github-webhook-config.ts","lineNumber":89,"sourceCode":"    this.name = \"GitHubWebhookRecoveryError\";\n  }\n}\n\ntype AppRequest = { fetch: typeof globalThis.fetch; appToken: string };\nconst GITHUB_DELIVERIES_URL = \"https://api.github.com/app/hook/deliveries\";\nconst MAX_DELIVERIES_BYTES = 262_144;\nconst MAX_DETAIL_BYTES = 1_048_576;\nconst MAX_RETRY_DELAY_MS = 86_400_000;\nconst ID_KEYS = new Set([\n  \"id\",\n  \"installation_id\",\n  \"repository_id\",\n  \"number\",\n  \"in_reply_to_id\",\n]);\n\nfunction invalidResponse(): never {\n  throw new GitHubWebhookRecoveryError(\n    \"github_webhook_recovery_invalid_response\",\n  );\n}\n\nfunction record(value: unknown): Record<string, unknown> {\n  if (!value || typeof value !== \"object\" || Array.isArray(value))\n    invalidResponse();\n  return value as Record<string, unknown>;\n}\n\nfunction decimalId(value: unknown): string {\n  if (\n    typeof value !== \"string\" ||\n    !/^[1-9][0-9]{0,19}$/.test(value) ||\n    (value.length === 20 && value > \"18446744073709551615\")\n  )\n    invalidResponse();\n  return value;","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-github-webhook-config.ts#L71-L107","documentation":"GitHubWebhookRecoveryError with code github_webhook_recovery_invalid_response is thrown by the invalidResponse() helper whenever a GitHub API response body fails strict validation (wrong JSON shape, oversized body, bad ID/timestamp/URL/enum format). It exists so the recovery layer never trusts malformed provider data and never leaks the raw payload. The message is intentionally generic — the actual offending value is discarded.","triggerScenarios":"Any GitHub webhook-recovery call (listGitHubAppWebhookDeliveries, readGitHubAppWebhookConfig, getGitHubAppWebhookDelivery, getGitHubRecoveryComment) whose response fails a validator: non-object/array body via record(), ID not matching /^[1-9][0-9]{0,19}$/, timestamp not GitHub ISO format, oversized body (content-length or streamed size over the per-endpoint max), invalid guid/redelivery/status_code/event/action, callback URL not https with no query/hash/credentials, bad repo full_name, Link header parse failures in nextCursor.","commonSituations":"GitHub API behavior changed or a proxy/gateway intercepts api.github.com and returns an HTML error page or a differently-shaped JSON; a corporate MITM proxy truncates the body; a mock/stub test server returns loose fixtures that violate the strict regexes; running on an older Node without JSON.parse context.source so rounded numeric IDs fail decimalId.","solutions":["Log only the error code (the raw response is intentionally discarded) and re-run the failing call while inspecting the raw HTTP traffic separately (curl or httpx) to see what GitHub actually returned.","Verify no proxy/firewall is rewriting api.github.com responses; set HTTPS_PROXY correctly or bypass it.","If a mock server is used in tests, update fixtures to match the strict validators (decimal-string IDs, GitHub ISO timestamps, https callback URL, valid guid, lowercase event names).","Ensure Node >=24.11 so JSON.parse supplies context.source for lossless ID recovery; upgrade the runtime if on older Node.","If the failure is persistent on one endpoint, check GitHub's status/changelog for a response-shape change and update the validator deliberately."],"exampleFix":"// before (test fixture that trips validators)\n{ \"id\": 12345678, \"delivered_at\": \"2024-01-01T00:00:00Z\", \"event\": \"push\", \"guid\": \"not-a-uuid\" }\n// after\n{ \"id\": \"12345678\", \"delivered_at\": \"2024-01-01T00:00:00.000Z\", \"event\": \"push\", \"guid\": \"0f5a1b2c-3d4e-5f60-7182-93a4b5c6d7e8\" }","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"function isRecoveryResponseError(e: unknown): e is GitHubWebhookRecoveryError {\n  return e instanceof GitHubWebhookRecoveryError && e.code === \"github_webhook_recovery_invalid_response\";\n}","tryCatchPattern":"try {\n  await listGitHubAppWebhookDeliveries({ fetch, appToken });\n} catch (e) {\n  if (isRecoveryResponseError(e)) {\n    log.warn(\"GitHub returned malformed webhook data; not retrying\", { code: e.code });\n  } else throw e;\n}","preventionTips":["Keep GitHub fixtures in tests aligned with the strict validators (string IDs, ISO timestamps, https URLs)","Monitor GitHub API changelog for response-shape changes","Bypass or correctly configure proxies that may rewrite api.github.com responses","Run on Node >=24.11 for lossless JSON.parse source recovery"],"tags":["validation","github-api","response-parsing","webhooks"],"backgroundTag":"unexpected-response-shape","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}