{"record":{"id":"f2af5bff8de6f64b","repo":"paperclipai/paperclip","slug":"github-webhook-configuration-is-incomplete","errorCode":null,"errorMessage":"GitHub webhook configuration is incomplete","messagePattern":"GitHub webhook configuration is incomplete","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/chat-github-webhook-config.ts","lineNumber":705,"sourceCode":" * A successful PATCH is configuration evidence only, never a signed ping or a\n * successful chat round trip. See https://docs.github.com/en/rest/apps/webhooks.\n */\nexport async function resyncGitHubAppWebhook(input: {\n  fetch: typeof globalThis.fetch;\n  appToken: string;\n  webhookUrl: string;\n  webhookSecret: string;\n}): Promise<void> {\n  const webhookUrl = new URL(input.webhookUrl);\n  if (\n    webhookUrl.protocol !== \"https:\" ||\n    webhookUrl.username ||\n    webhookUrl.password ||\n    webhookUrl.search ||\n    webhookUrl.hash ||\n    !input.webhookSecret\n  ) {\n    throw new Error(\"GitHub webhook configuration is incomplete\");\n  }\n\n  let response: Response;\n  try {\n    response = await input.fetch(GITHUB_APP_WEBHOOK_CONFIG_URL, {\n      method: \"PATCH\",\n      redirect: \"error\",\n      signal: AbortSignal.timeout(25_000),\n      headers: {\n        accept: \"application/vnd.github+json\",\n        authorization: `Bearer ${input.appToken}`,\n        \"content-type\": \"application/json\",\n        \"x-github-api-version\": \"2022-11-28\",\n      },\n      body: JSON.stringify({\n        url: input.webhookUrl,\n        content_type: \"json\",\n        insecure_ssl: \"0\",","sourceCodeStart":687,"sourceCodeEnd":723,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-github-webhook-config.ts#L687-L723","documentation":"resyncGitHubAppWebhook validates the webhook URL and secret before PATCHing https://api.github.com/app/hook/config, throwing a plain Error('GitHub webhook configuration is incomplete') if the URL is not a clean https URL (no credentials/query/hash) or the webhookSecret is empty. This is a local configuration guard — no network request is made.","triggerScenarios":"webhookUrl passed as http://, with an embedded user:pass, with ?query or #fragment, or not a parseable URL (new URL throws before the check); webhookSecret empty string, undefined, or a placeholder not yet configured.","commonSituations":"Dev environment running with http://localhost callback (not https); secret not provisioned from env/secret manager (empty GITHUB_APP_WEBHOOK_SECRET); a tunneling URL like https://tunnel.example/cb?token=xyz containing a query string; forgetting to URL-encode/remove fragments.","solutions":["Supply an https:// URL with no query string, fragment, or embedded credentials — put any token in the path instead.","Provision the webhook secret: check the env var/secret manager is set and non-empty before calling resync.","Normalize the URL: strip trailing query/hash, ensure scheme is https, then pass the cleaned value.","Fail fast in your own startup code with a clear message if webhookUrl/webhookSecret are missing or non-https."],"exampleFix":"// before\nawait resyncGitHubAppWebhook({ fetch, appToken, webhookUrl: process.env.WEBHOOK_URL, webhookSecret: process.env.WEBHOOK_SECRET });\n// after\nconst webhookUrl = process.env.WEBHOOK_URL;\nconst webhookSecret = process.env.WEBHOOK_SECRET;\nconst u = new URL(webhookUrl);\nif (u.protocol !== \"https:\" || u.search || u.hash || u.username || u.password || !webhookSecret) {\n  throw new Error(\"WEBHOOK_URL must be a clean https URL and WEBHOOK_SECRET must be set\");\n}\nawait resyncGitHubAppWebhook({ fetch, appToken, webhookUrl, webhookSecret });","handlingStrategy":"validation","validationCode":"function validateWebhookConfig(webhookUrl: string, webhookSecret: string): void {\n  const u = new URL(webhookUrl);\n  if (u.protocol !== \"https:\" || u.username || u.password || u.search || u.hash) throw new Error(\"webhookUrl must be a clean https URL\");\n  if (!webhookSecret) throw new Error(\"webhookSecret is required\");\n}","typeGuard":"null","tryCatchPattern":"try {\n  await resyncGitHubAppWebhook({ fetch, appToken, webhookUrl, webhookSecret });\n} catch (e) {\n  if (e instanceof Error && e.message === \"GitHub webhook configuration is incomplete\") {\n    throw new ConfigError(\"Set WEBHOOK_URL (https, no query/hash/credentials) and WEBHOOK_SECRET before resync\");\n  }\n  throw e;\n}","preventionTips":["Require an https callback URL with tokens in the path, never in the query string","Fail fast at startup when WEBHOOK_SECRET is unset instead of resyncing with an empty secret","Validate URL shape with new URL() before calling resync","Keep the webhook secret in a secret manager, not inline config files"],"tags":["configuration","validation","github-api","webhooks"],"backgroundTag":"invalid-config-value","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"}