{"record":{"id":"0f8aa3fa1787397f","repo":"thedotmack/claude-mem","slug":"badrequest-0f8aa3","errorCode":"BadRequest","errorMessage":"Legacy /api/sessions/summarize requires a project-scoped API key","messagePattern":"Legacy /api/sessions/summarize requires a project-scoped API key","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"src/server/compat/SessionsSummarizeAdapter.ts","lineNumber":63,"sourceCode":"      authMode: this.options.authMode,\n      allowLocalDevBypass: this.options.allowLocalDevBypass,\n      requiredScopes: ['memories:write'],\n    });\n\n    app.post('/api/sessions/summarize', writeAuth, this.asyncHandler(async (req, res) => {\n      const parsed = summarizeSchema.safeParse(req.body);\n      if (!parsed.success) {\n        res.status(400).json({ error: 'ValidationError', issues: parsed.error.issues });\n        return;\n      }\n      const teamId = req.authContext?.teamId ?? null;\n      const projectId = req.authContext?.projectId ?? null;\n      if (!teamId) {\n        res.status(403).json({ error: 'Forbidden', message: 'API key is not bound to a team' });\n        return;\n      }\n      if (!projectId) {\n        res.status(400).json({\n          error: 'BadRequest',\n          message: 'Legacy /api/sessions/summarize requires a project-scoped API key',\n        });\n        return;\n      }\n\n      // Subagent contexts in legacy code emit summarize calls but the worker\n      // skipped them. We preserve the legacy semantics so existing clients\n      // see the same response shape.\n      if (parsed.data.agentId) {\n        res.json({ status: 'skipped', reason: 'subagent_context' });\n        return;\n      }\n\n      try {\n        await this.summarizeSession(req, res, parsed.data, teamId, projectId);\n      } catch (error) {\n        logger.error('SYSTEM', 'compat summarize adapter failed', {","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/server/compat/SessionsSummarizeAdapter.ts#L45-L81","documentation":"Returned by the legacy POST /api/sessions/summarize compat route when the key is team-bound but has no project binding. Legacy semantics pin every summarize call to one project, so req.authContext.projectId must be non-null; a team-wide key fails this check with 400 even though it passed the team check.","triggerScenarios":"Calling /api/sessions/summarize with a team-scoped API key (no projectId on the key record); reusing a key minted for team-level read endpoints on the legacy compat route; upgrading a deployment to project-scoped keys while the client still holds an old team-only key.","commonSituations":"The same key works on /v1/events (project in body) but fails on the legacy route; ops minted a broad team key for convenience and legacy Claude Code clients then break with 400.","solutions":["Use a project-scoped API key: create one via POST /v1/keys with both the team and the target projectId.","If the key should cover one project, rebind it so its projectId is set.","Confirm the key's projectId via key introspection before switching the client to the legacy endpoint.","Move the client to the modern route that accepts projectId in the payload instead of relying on key scope."],"exampleFix":"// before: team-scoped key -> 400 BadRequest\nconst res = await fetch(`${base}/api/sessions/summarize`, {\n  method: 'POST',\n  headers: { Authorization: `Bearer ${TEAM_KEY}` },\n  body: JSON.stringify(payload),\n});\n\n// after: mint a project-scoped key for the legacy route\n// POST /v1/keys { teamId, projectId, scopes: ['memories:write'] }\nconst res = await fetch(`${base}/api/sessions/summarize`, {\n  method: 'POST',\n  headers: { Authorization: `Bearer ${PROJECT_SCOPED_KEY}` },\n  body: JSON.stringify(payload),\n});","handlingStrategy":"validation","validationCode":"function assertProjectScopedKey(keyMeta: { teamId: string | null; projectId: string | null }) {\n  if (!keyMeta.teamId || !keyMeta.projectId) {\n    throw new Error('Legacy /api/sessions/summarize needs a project-scoped (team+project bound) API key');\n  }\n}","typeGuard":"interface CompatBadRequest { error: string; message: string }\nfunction isProjectScopeRequired(body: unknown): body is CompatBadRequest {\n  return (\n    typeof body === 'object' && body !== null &&\n    (body as CompatBadRequest).error === 'BadRequest' &&\n    (body as CompatBadRequest).message?.includes('project-scoped API key')\n  );\n}","tryCatchPattern":"if (res.status === 400) {\n  const body = await res.json();\n  if (isProjectScopeRequired(body)) {\n    // key scope problem, not payload: remint key, never resend same request\n  }\n}","preventionTips":["Document which endpoints need project-scoped keys and label stored keys with their scope.","Automate key minting with the exact scopes each client needs (memories:write + projectId)."],"tags":["auth","api-key","http-400","claude-code-compat","project-scope"],"backgroundTag":"api-key-scope-mismatch","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}