{"record":{"id":"8bb73e7c9bb25343","repo":"paperclipai/paperclip","slug":"review-decision-was-not-accepted-response-status-await","errorCode":null,"errorMessage":"Review decision was not accepted (${response.status}): ${(await response.text()).slice(0, 2_000)}","messagePattern":"Review decision was not accepted \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/paperclip-runner-tool-authority.ts","lineNumber":646,"sourceCode":"    });\n    if (!review) throw forbidden(\"Review is no longer assigned to this run.\");\n    const expectedStatus = input.decision === \"accept\" ? \"accepted\" : \"rejected\";\n    if (review.interaction.status !== \"pending\") {\n      if (review.interaction.status !== expectedStatus) throw badRequest(\"This review already has a different decision.\");\n      return { interactionId: review.interaction.id, status: expectedStatus, deduplicated: true };\n    }\n    const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;\n    const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId,\n      context.actor.adapterType, this.binding.runId, context.run.responsibleUserId);\n    if (!apiUrl || !token) throw new Error(\"Review tool authentication is unavailable\");\n    // Use the existing resolution route so authorization, activity, dependency\n    // wakes and request-changes continuation have one implementation.\n    const response = await fetch(`${apiUrl.replace(/\\/$/, \"\")}/api/issues/${this.binding.issueId}/interactions/${review.interaction.id}/${input.decision}`, {\n      method: \"POST\", redirect: \"error\", signal: AbortSignal.timeout(30_000),\n      headers: { Authorization: `Bearer ${token}`, \"Content-Type\": \"application/json\", \"X-Paperclip-Run-Id\": this.binding.runId },\n      body: JSON.stringify(input.decision === \"reject\" ? { reason } : {}),\n    });\n    if (!response.ok) throw new Error(`Review decision was not accepted (${response.status}): ${(await response.text()).slice(0, 2_000)}`);\n    return { interactionId: review.interaction.id, status: expectedStatus };\n  }\n\n  async #reportProgress(input: Record<string, unknown>): Promise<unknown> {\n    const body = typeof input.body === \"string\" ? input.body.trim() : \"\";\n    const idempotencyKey = typeof input.idempotencyKey === \"string\" ? input.idempotencyKey.trim() : \"\";\n    if (!body || !idempotencyKey) throw new Error(\"paperclip_runner_tool_input_invalid\");\n    let publication: Awaited<ReturnType<typeof persistActivity>>[\"publication\"] | null = null;\n    const result = await this.#withMutationReceipt(\n      \"report_progress\",\n      idempotencyKey,\n      input,\n      async (tx, context) => {\n        const comment = await issueService(tx).addComment(\n          this.binding.issueId,\n          body,\n          { agentId: this.binding.agentId, runId: this.binding.runId },\n          { authorizationReason: \"paperclip_runner_protocol\" },","sourceCodeStart":628,"sourceCodeEnd":664,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/native-runtime/paperclip-runner-tool-authority.ts#L628-L664","documentation":"In #resolveReview, after validating the accept/reject decision locally, the runner posts the decision to the issue interaction resolution route (`/api/issues/:id/interactions/:interactionId/accept|reject`) using a locally minted agent JWT. If the HTTP response is not ok, the error is thrown with the upstream status and up to 2000 chars of the response body, so the agent sees exactly why the control plane refused the decision.","triggerScenarios":"Calling the resolve_review runner tool when the API returns a non-2xx: e.g. 401 because the locally minted createLocalAgentJwt is rejected (PAPERCLIP_API_URL mismatch or secret skew), 403 because the run is no longer authorized for the interaction, 404 because the interaction id does not exist on that issue, 409 from a concurrent decision, or a 30x redirect (redirect: 'error').","commonSituations":"PAPERCLIP_API_URL set to the wrong host/port or missing trailing protocol, the review interaction being resolved by the board between the status check and the POST, stale run admission after a run restart, or the API being behind a proxy that returns HTML error pages.","solutions":["Check the status and body text embedded in the error message; fix the root cause on the control-plane side (auth, permissions, or interaction state) and retry the tool call.","Verify PAPERCLIP_API_URL (or binding.apiUrl) points at the reachable API base with no trailing slash issues and correct scheme.","Confirm the run is still the issue's executionRunId and the review interaction is still pending; if the board already decided, re-fetch assignment state instead of retrying blind.","For 401s, ensure the local agent JWT secret/config matches the API server so createLocalAgentJwt tokens validate.","Retry only after fixing the cause; the operation is POSTed to a resolution route and may not be idempotent across differing decisions."],"exampleFix":"// before: blind retry loop on failure\nwhile (!ok) { await resolveReview({ decision: 'accept' }); }\n\n// after: inspect status and stop on permanent failures\nif (err.message.includes('(401)') || err.message.includes('(403)')) {\n  throw err; // fix auth/authorization first, do not retry\n}","handlingStrategy":"try-catch","validationCode":"const apiUrl = binding.apiUrl ?? process.env.PAPERCLIP_API_URL;\nif (!apiUrl || !/^https?:\\/\\//.test(apiUrl)) throw new Error('PAPERCLIP_API_URL not configured');\nif (!review || review.interaction.status !== 'pending') throw new Error('review not pending for this run');","typeGuard":null,"tryCatchPattern":"try {\n  await resolveReview({ decision });\n} catch (err) {\n  const m = /\\((\\d{3})\\)/.exec(err.message);\n  if (m && ['401','403','404','409'].includes(m[1])) {\n    // permanent: refresh assignment/auth, do not blind-retry\n    await refreshReviewState();\n  }\n  throw err;\n}","preventionTips":["Verify PAPERCLIP_API_URL reachability before run start","Check interaction status is pending before resolving","Keep agent JWT secret config in sync between runner and API","Never retry resolveReview on 4xx without changing state"],"tags":["http","authorization","review-workflow","runner-tools"],"backgroundTag":"http-error-response","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}