Task: 21051318-15ae-4d0e-a9a1-2143f87de759 API client, auth bootstrap and job polling hook.
What changed: web/src/api adds apiFetch (same-origin, CSRF from cached /me, typed ApiError), 401 redirect, csrf refresh-and-retry once, and a not-in-this-household screen for other 403s. App bootstrap loads /me behind a Skeleton. useJob polls /api/v1/jobs/{id} with 500 ms to 2 s backoff and stops on terminal state or unmount.
Done-condition: Vitest with stubbed fetch covers CSRF, 401, csrf retry, 403 screen, and useJob. Developer reports lint:colors, typecheck, test, and build passed.
Base: feature/64e5bb19-c9a2-4c6a-8f0c-aceae43fb732-frontend-foundation at c899f07b1f.
Task: 21051318-15ae-4d0e-a9a1-2143f87de759 API client, auth bootstrap and job polling hook.
What changed: web/src/api adds apiFetch (same-origin, CSRF from cached /me, typed ApiError), 401 redirect, csrf refresh-and-retry once, and a not-in-this-household screen for other 403s. App bootstrap loads /me behind a Skeleton. useJob polls /api/v1/jobs/{id} with 500 ms to 2 s backoff and stops on terminal state or unmount.
Done-condition: Vitest with stubbed fetch covers CSRF, 401, csrf retry, 403 screen, and useJob. Developer reports lint:colors, typecheck, test, and build passed.
Base: feature/64e5bb19-c9a2-4c6a-8f0c-aceae43fb732-frontend-foundation at c899f07b1fae3daea68ba41e14134693a3f38004.
web/src/api provides createApiClient().apiFetch: same-origin JSON with
the cached /api/v1/me CSRF token on non-GET requests and a typed
ApiError from the server's error body. A 401 navigates to
/auth/login?return={path}; a csrf 403 refreshes /me once and retries
once; any other 403 shows the "not in this household" screen.
App loads /me before the routes, showing the Skeleton meanwhile. The
QueryClient retries only ApiErrors the server marks retryable. useJob
polls GET /api/v1/jobs/{id} backing off 500 ms to 2 s, exposing status,
partial result and error until a terminal status, and stops on unmount.
Maestro task 21051318-15ae-4d0e-a9a1-2143f87de759.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
{"verdict":"CHANGES_REQUIRED","reviewed_sha":"913c1e5337f77410f6c61e8bdbeb57adb3f5f588","summary":"CSRF handling, auth bootstrap, and terminal job polling match the requested behavior. The color lint passed. Web dependencies are absent, so typecheck, tests, and build were not run.","findings":[{"id":"job-poll-stops-before-terminal","severity":"medium","location":"web/src/api/useJob.ts:57","failure_case":"If a job is running and one poll fails, polling stops permanently. The hook can remain at status running without ever observing the job's terminal state.","evidence":"The catch block records the request error but schedules no further poll. The test at useJob.test.tsx:88 explicitly expects polling to stop after a failed request.","suggested_correction":"Expose the request error and continue polling with bounded backoff until the job reaches succeeded or failed, the job ID changes, or the hook unmounts. Add a test where a failed poll is followed by a terminal response."}]}
Posted unaltered by review_agent. This is transport, not a second review.
## Independent review — Codex `gpt-6-sol`, reasoning high
Verdict: **CHANGES_REQUIRED**
Reviewed SHA: `913c1e5337f77410f6c61e8bdbeb57adb3f5f588`
```json
{
"verdict": "CHANGES_REQUIRED",
"reviewed_sha": "913c1e5337f77410f6c61e8bdbeb57adb3f5f588",
"summary": "CSRF handling, auth bootstrap, and terminal job polling match the requested behavior. The color lint passed. Web dependencies are absent, so typecheck, tests, and build were not run.",
"findings": [
{
"id": "job-poll-stops-before-terminal",
"severity": "medium",
"location": "web/src/api/useJob.ts:57",
"failure_case": "If a job is running and one poll fails, polling stops permanently. The hook can remain at status running without ever observing the job's terminal state.",
"evidence": "The catch block records the request error but schedules no further poll. The test at useJob.test.tsx:88 explicitly expects polling to stop after a failed request.",
"suggested_correction": "Expose the request error and continue polling with bounded backoff until the job reaches succeeded or failed, the job ID changes, or the hook unmounts. Add a test where a failed poll is followed by a terminal response."
}
]
}
```
---
Posted unaltered by `review_agent`. This is transport, not a second review.
A failed GET /api/v1/jobs/{id} no longer ends polling. useJob exposes the
request's error, keeps the last known status and result, and schedules the
next poll on the same 500 ms to 2 s backoff. Polling stops only at a
terminal status, a changed job id or unmount.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
{"verdict":"APPROVED","reviewed_sha":"690e1056a6cf2d8a2a9ef7c2b7f8be02c3388541","summary":"No findings in the reviewed diff. The prior failed-poll issue is fixed: polling continues after a request error and stops on a terminal job state or unmount. Color lint passed. Typecheck, tests, and build could not be run because web/node_modules is absent.","findings":[]}
Posted unaltered by review_agent. This is transport, not a second review.
## Independent review — Codex `gpt-6-sol`, reasoning high
Verdict: **APPROVED**
Reviewed SHA: `690e1056a6cf2d8a2a9ef7c2b7f8be02c3388541`
```json
{
"verdict": "APPROVED",
"reviewed_sha": "690e1056a6cf2d8a2a9ef7c2b7f8be02c3388541",
"summary": "No findings in the reviewed diff. The prior failed-poll issue is fixed: polling continues after a request error and stops on a terminal job state or unmount. Color lint passed. Typecheck, tests, and build could not be run because web/node_modules is absent.",
"findings": []
}
```
---
Posted unaltered by `review_agent`. This is transport, not a second review.
approver_agent
merged commit 048d5dd71a into feature/64e5bb19-c9a2-4c6a-8f0c-aceae43fb732-frontend-foundation2026-10-04 08:46:41 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Task: 21051318-15ae-4d0e-a9a1-2143f87de759 API client, auth bootstrap and job polling hook.
What changed: web/src/api adds apiFetch (same-origin, CSRF from cached /me, typed ApiError), 401 redirect, csrf refresh-and-retry once, and a not-in-this-household screen for other 403s. App bootstrap loads /me behind a Skeleton. useJob polls /api/v1/jobs/{id} with 500 ms to 2 s backoff and stops on terminal state or unmount.
Done-condition: Vitest with stubbed fetch covers CSRF, 401, csrf retry, 403 screen, and useJob. Developer reports lint:colors, typecheck, test, and build passed.
Base: feature/64e5bb19-c9a2-4c6a-8f0c-aceae43fb732-frontend-foundation at
c899f07b1f.web/src/api provides createApiClient().apiFetch: same-origin JSON with the cached /api/v1/me CSRF token on non-GET requests and a typed ApiError from the server's error body. A 401 navigates to /auth/login?return={path}; a csrf 403 refreshes /me once and retries once; any other 403 shows the "not in this household" screen. App loads /me before the routes, showing the Skeleton meanwhile. The QueryClient retries only ApiErrors the server marks retryable. useJob polls GET /api/v1/jobs/{id} backing off 500 ms to 2 s, exposing status, partial result and error until a terminal status, and stops on unmount. Maestro task 21051318-15ae-4d0e-a9a1-2143f87de759. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Independent review — Codex
gpt-6-sol, reasoning highVerdict: CHANGES_REQUIRED
Reviewed SHA:
913c1e5337f77410f6c61e8bdbeb57adb3f5f588Posted unaltered by
review_agent. This is transport, not a second review.A failed GET /api/v1/jobs/{id} no longer ends polling. useJob exposes the request's error, keeps the last known status and result, and schedules the next poll on the same 500 ms to 2 s backoff. Polling stops only at a terminal status, a changed job id or unmount. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Independent review — Codex
gpt-6-sol, reasoning highVerdict: APPROVED
Reviewed SHA:
690e1056a6cf2d8a2a9ef7c2b7f8be02c3388541Posted unaltered by
review_agent. This is transport, not a second review.