{"record":{"id":"9738a9950b0904bc","repo":"can1357/oh-my-pi","slug":"unauthenticated","errorCode":null,"errorMessage":"unauthenticated","messagePattern":"unauthenticated","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"python/robomp/src/proxy/server.py","lineNumber":460,"sourceCode":"    async def _authenticate(request: Request) -> bytes:\n        body = await _read_body_capped(request)\n        ts = request.headers.get(HEADER_TIMESTAMP)\n        sig = request.headers.get(HEADER_SIGNATURE)\n        target = _request_target(request)\n        result = verify(\n            method=request.method,\n            path=target,\n            body=body,\n            timestamp=ts,\n            signature=sig,\n            key=_resolve_hmac_key(settings),\n        )\n        if not result.ok:\n            log.warning(\n                \"gh-proxy auth rejected\",\n                extra={\"reason\": result.reason, \"path\": request.url.path},\n            )\n            raise HTTPException(status.HTTP_401_UNAUTHORIZED, \"unauthenticated\")\n        return body\n\n    # ---- meta ----\n    @app.get(\"/healthz\")\n    async def healthz() -> dict[str, str]:\n        return {\"status\": \"ok\"}\n\n    # ---- reads ----\n    @app.get(\"/gh/v1/authenticated_login\")\n    async def authenticated_login(request: Request) -> dict[str, str]:\n        await _authenticate(request)\n        github: GitHubClient = request.app.state.github\n        try:\n            login = await github.get_authenticated_login()\n        except GitHubError as exc:\n            raise HTTPException(exc.status, exc.message) from exc\n        return {\"login\": login}\n","sourceCodeStart":442,"sourceCodeEnd":478,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/proxy/server.py#L442-L478","documentation":"_authenticate validates the caller's GitHub token via the configured auth verifier; when the verification result is not ok, the proxy logs the reason and returns HTTP 401 'unauthenticated'. Every data endpoint (get_repo, list_workflow_runs, get_job_log_tail, etc.) calls this before doing work.","triggerScenarios":"Calling any /gh/v1/* endpoint without a token, with an expired/revoked GitHub PAT, with a token from the wrong scope/issuer, or with a malformed Authorization header the verifier rejects.","commonSituations":"Expired classic PATs (or fine-grained tokens past their expiration); missing GH_TOKEN in a new environment/CI job; token rotated but the proxy client still uses the old value; connecting to the proxy from a host not on its allowlist.","solutions":["Set/supply a current, valid GitHub token in the Authorization header the proxy expects","Verify the token works: curl -H \"Authorization: Bearer $TOKEN\" https://api.github.com/user","Regenerate the PAT if expired or revoked and update the stored secret","Check proxy logs ('gh-proxy auth rejected' reason field) for the specific rejection cause"],"exampleFix":"// before\nfetch(\"http://proxy/gh/v1/repo?repo=o/r\")\n// after\nfetch(\"http://proxy/gh/v1/repo?repo=o/r\", {\n  headers: { Authorization: `Bearer ${process.env.GH_PROXY_TOKEN}` },\n})","handlingStrategy":"try-catch","validationCode":"// pre-check token shape before calling\nif (!process.env.GH_PROXY_TOKEN) throw new Error(\"GH_PROXY_TOKEN not set\");","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(proxy + \"/gh/v1/repo?repo=o/r\", { headers: { Authorization: `Bearer ${token}` } });\n  if (res.status === 401) {\n    // token missing/expired/revoked: prompt re-auth or fail with clear message\n    throw new Error(\"proxy rejected credentials — refresh your GitHub token\");\n  }\n} catch (err) { /* handle */ }","preventionTips":["Rotate and store tokens with expiry tracking","Export the token in every environment (CI, shells, containers)","Verify tokens with a cheap upstream call (GET /user) before long operations","Read the proxy's 'gh-proxy auth rejected' log reason for diagnosis"],"tags":["http-401","authentication","github-token"],"backgroundTag":"authentication-required","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}