feat: persist authenticated agent task reports#426
Conversation
Add Bearer token auth to POST /api/agents/[agentName]/tasks/report.
Persist one AgentRun row per valid report with:
- agentName from route param
- runType from taskType
- status derived from outcome (failed->failed, blocked->blocked, else completed)
- issueId resolved from repoFullName + issueNumber when matching Issue exists
- touchedIssueUrls built from issue and PR references
- summary and errorMessage from report fields
Response includes { ok, agentName, report, agentRunId }.
Validation failures and unauthorized requests do not create AgentRun.
Closes #413
Closes #409
There was a problem hiding this comment.
AI Automated Review
Full PR review.
Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)
PR Review: PR 426 — feat: persist authenticated agent task reports
Recommendation: Approve
This PR correctly addresses both linked issues (PR 409, PR 413) by adding Bearer token authentication and persisting AgentRun rows from task reports. The implementation is internally consistent, follows repository conventions, and includes comprehensive test coverage.
Required Checks
✅ verify route access controls are in place
Verified. The route now calls authorizeRequest(request) at the start of the POST handler and returns 401 Unauthorized when authentication fails:
// Authenticate
if (!(await authorizeRequest(request)).authorized) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}This uses the shared authorizeRequest helper from @/lib/auth, which is the documented pattern for protecting mutating Dispatch API routes. The auth check occurs before validation or persistence, ensuring no data is written for unauthenticated requests.
Test coverage confirms:
returns 401 when no authorization header is providedreturns 401 when token is incorrectunauthorized requests do not create AgentRun
✅ check for unintended public endpoints
Verified. The endpoint is not publicly accessible after this change. Anonymous requests without a valid Bearer token receive 401. The DISPATCH_AGENT_TOKEN is required, matching the documented contract for agent API bearer auth.
Change-by-Change Findings
| File | Finding | Severity | Category |
|---|---|---|---|
route.ts |
Auth added before validation — correctly ordered | — | — |
route.ts |
deriveStatus() maps outcomes to statuses: failed→failed, blocked→blocked, else→completed |
— | — |
route.ts |
resolveIssueId() does two DB lookups (repo, then issue) — graceful null on missing |
— | — |
route.ts |
buildTouchedUrls() constructs GitHub URLs from report fields |
— | — |
route.ts |
AgentRun created only after all validation passes | — | — |
route.ts |
Response includes agentRunId: run.id for caller traceability |
— | — |
route.test.ts |
Test file restructured into logical describe blocks: auth, validation, persistence |
— | — |
route.test.ts |
Auth tests mock isAuthorizedAgentToken / isAuthorizedBearerToken to isolate behavior |
— | — |
route.test.ts |
No findings — tests are comprehensive and correctly assert 401/400/200 paths | — | — |
Standards Compliance
| Standard | Status | Evidence |
|---|---|---|
Bearer token auth via DISPATCH_AGENT_TOKEN |
✅ | authorizeRequest(request) from @/lib/auth |
| Return 401 for unauthenticated | ✅ | Explicit NextResponse.json({ error: "Unauthorized" }, { status: 401 }) |
| Return 400 for validation errors | ✅ | Preserved from original implementation |
| No secrets in responses | ✅ | Test: "does not echo secrets or auth data" |
| AuditLog for state changes | AgentRun persisted, but no AuditLog entry per issue PR 413's consideration of "both" |
Note on AuditLog: Issue PR 413 asks to "decide the persistence model (audit log row, agent run row, both)." The PR chose AgentRun only. Given that POST /api/agent-runs also does not write AuditLog (per repo grep), this is consistent with existing patterns. The audit label on PR 413 suggests future work could add AuditLog, but this is out of scope for the immediate fix.
Linked Issue Fit
| Issue | Acceptance Criteria | Verification |
|---|---|---|
| PR 409 (P1): Add authentication or remove endpoint | Add authorizeRequest() call |
✅ Added; test confirms 401 for missing/invalid token |
| PR 413 (P2): Decide persistence model and add tests | Persist data + tests | ✅ AgentRun created per report; comprehensive tests added |
Evidence Provider Findings
No evidence providers were configured for this PR.
Tool Harness Findings
The tool harness read route.ts and src/lib/auth.ts to verify the auth implementation. Findings:
authorizeRequestis imported from@/lib/auth— correctauthorizeRequestreturns{ authorized: boolean, ... }— route checks.authorizedsrc/lib/auth.tsconfirms legacy mode (noDISPATCH_AUTH_MODEset) uses Bearer token checks — consistent with agent API contract
CI Check Results
| Check | Status |
|---|---|
| Docker Build | ✅ success |
| Validate | ✅ success |
Unknowns / Needs Verification
None. The diff, tests, and auth implementation are fully traceable.
Summary
This PR is a clean, well-tested fix that closes both PR 409 (authentication vulnerability) and PR 413 (persistence gap). The route is no longer publicly accessible, and task reports are now persisted as AgentRun rows for operator traceability.
Persist authenticated agent task reports as AgentRun rows.
Changes
POST /api/agents/[agentName]/tasks/reportusing existingauthorizeRequesthelperAgentRunrow per valid report:agentName: from route paramrunType: fromtaskTypestatus: derived from outcome (failed→failed,blocked→blocked, everything else →completed)startedAt/finishedAt: current timestampsummary: report summary, if providederrorMessage: report error, if providedtouchedIssueUrls: built from issue and PR referencesissueId: resolved fromrepoFullName + issueNumberwhen matching Issue exists; otherwise null{ ok, agentName, report, agentRunId }Validation
Closes #413
Closes #409