feat: persist authenticated agent task reports - #426

Merged
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report
Jun 17, 2026
Merged

feat: persist authenticated agent task reports#426
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Persist authenticated agent task reports as AgentRun rows.

Changes

  • Add Bearer token auth to POST /api/agents/[agentName]/tasks/report using existing authorizeRequest helper
  • Persist one AgentRun row per valid report:
    • agentName: from route param
    • runType: from taskType
    • status: derived from outcome (failedfailed, blockedblocked, everything else → completed)
    • startedAt / finishedAt: current timestamp
    • summary: report summary, if provided
    • errorMessage: report error, if provided
    • touchedIssueUrls: built from issue and PR references
    • issueId: resolved from repoFullName + issueNumber when matching Issue exists; otherwise null
  • Response includes { ok, agentName, report, agentRunId }
  • Validation failures and unauthorized requests return early without creating AgentRun
  • All existing validation behavior preserved (400 for invalid types, 401 for unauthenticated)

Validation

  • npm run lint ✅
  • npm run typecheck ✅
  • npm run test (1335 tests) ✅
  • npm run build ✅

Closes#413
Closes#409

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#413Closes#409

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

// Authenticateif(!(awaitauthorizeRequest(request)).authorized){returnNextResponse.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 provided
  • returns 401 when token is incorrect
  • unauthorized 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

FileFindingSeverityCategory
route.tsAuth added before validation — correctly ordered
route.tsderiveStatus() maps outcomes to statuses: failed→failed, blocked→blocked, else→completed
route.tsresolveIssueId() does two DB lookups (repo, then issue) — graceful null on missing
route.tsbuildTouchedUrls() constructs GitHub URLs from report fields
route.tsAgentRun created only after all validation passes
route.tsResponse includes agentRunId: run.id for caller traceability
route.test.tsTest file restructured into logical describe blocks: auth, validation, persistence
route.test.tsAuth tests mock isAuthorizedAgentToken / isAuthorizedBearerToken to isolate behavior
route.test.tsNo findings — tests are comprehensive and correctly assert 401/400/200 paths

Standards Compliance

StandardStatusEvidence
Bearer token auth via DISPATCH_AGENT_TOKENauthorizeRequest(request) from @/lib/auth
Return 401 for unauthenticatedExplicit NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Return 400 for validation errorsPreserved from original implementation
No secrets in responsesTest: "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

IssueAcceptance CriteriaVerification
PR 409 (P1): Add authentication or remove endpointAdd authorizeRequest() call✅ Added; test confirms 401 for missing/invalid token
PR 413 (P2): Decide persistence model and add testsPersist 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:

  • authorizeRequest is imported from @/lib/auth — correct
  • authorizeRequest returns { authorized: boolean, ... } — route checks .authorized
  • src/lib/auth.ts confirms legacy mode (no DISPATCH_AUTH_MODE set) uses Bearer token checks — consistent with agent API contract

CI Check Results

CheckStatus
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.

@joryirving
joryirving merged commit 2cdda7b into mainJun 17, 2026
3 checks passed
@joryirving
joryirving deleted the feat/persist-agent-task-report branch June 17, 2026 15:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: persist authenticated agent task reports - #426

Merged
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report
Jun 17, 2026
Merged

feat: persist authenticated agent task reports#426
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Persist authenticated agent task reports as AgentRun rows.

Changes

  • Add Bearer token auth to POST /api/agents/[agentName]/tasks/report using existing authorizeRequest helper
  • Persist one AgentRun row per valid report:
    • agentName: from route param
    • runType: from taskType
    • status: derived from outcome (failedfailed, blockedblocked, everything else → completed)
    • startedAt / finishedAt: current timestamp
    • summary: report summary, if provided
    • errorMessage: report error, if provided
    • touchedIssueUrls: built from issue and PR references
    • issueId: resolved from repoFullName + issueNumber when matching Issue exists; otherwise null
  • Response includes { ok, agentName, report, agentRunId }
  • Validation failures and unauthorized requests return early without creating AgentRun
  • All existing validation behavior preserved (400 for invalid types, 401 for unauthenticated)

Validation

  • npm run lint ✅
  • npm run typecheck ✅
  • npm run test (1335 tests) ✅
  • npm run build ✅

Closes#413
Closes#409

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#413Closes#409

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

// Authenticateif(!(awaitauthorizeRequest(request)).authorized){returnNextResponse.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 provided
  • returns 401 when token is incorrect
  • unauthorized 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

FileFindingSeverityCategory
route.tsAuth added before validation — correctly ordered
route.tsderiveStatus() maps outcomes to statuses: failed→failed, blocked→blocked, else→completed
route.tsresolveIssueId() does two DB lookups (repo, then issue) — graceful null on missing
route.tsbuildTouchedUrls() constructs GitHub URLs from report fields
route.tsAgentRun created only after all validation passes
route.tsResponse includes agentRunId: run.id for caller traceability
route.test.tsTest file restructured into logical describe blocks: auth, validation, persistence
route.test.tsAuth tests mock isAuthorizedAgentToken / isAuthorizedBearerToken to isolate behavior
route.test.tsNo findings — tests are comprehensive and correctly assert 401/400/200 paths

Standards Compliance

StandardStatusEvidence
Bearer token auth via DISPATCH_AGENT_TOKENauthorizeRequest(request) from @/lib/auth
Return 401 for unauthenticatedExplicit NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Return 400 for validation errorsPreserved from original implementation
No secrets in responsesTest: "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

IssueAcceptance CriteriaVerification
PR 409 (P1): Add authentication or remove endpointAdd authorizeRequest() call✅ Added; test confirms 401 for missing/invalid token
PR 413 (P2): Decide persistence model and add testsPersist 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:

  • authorizeRequest is imported from @/lib/auth — correct
  • authorizeRequest returns { authorized: boolean, ... } — route checks .authorized
  • src/lib/auth.ts confirms legacy mode (no DISPATCH_AUTH_MODE set) uses Bearer token checks — consistent with agent API contract

CI Check Results

CheckStatus
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.

@joryirving
joryirving merged commit 2cdda7b into mainJun 17, 2026
3 checks passed
@joryirving
joryirving deleted the feat/persist-agent-task-report branch June 17, 2026 15:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: persist authenticated agent task reports - #426

Merged
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report
Jun 17, 2026
Merged

feat: persist authenticated agent task reports#426
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Persist authenticated agent task reports as AgentRun rows.

Changes

  • Add Bearer token auth to POST /api/agents/[agentName]/tasks/report using existing authorizeRequest helper
  • Persist one AgentRun row per valid report:
    • agentName: from route param
    • runType: from taskType
    • status: derived from outcome (failedfailed, blockedblocked, everything else → completed)
    • startedAt / finishedAt: current timestamp
    • summary: report summary, if provided
    • errorMessage: report error, if provided
    • touchedIssueUrls: built from issue and PR references
    • issueId: resolved from repoFullName + issueNumber when matching Issue exists; otherwise null
  • Response includes { ok, agentName, report, agentRunId }
  • Validation failures and unauthorized requests return early without creating AgentRun
  • All existing validation behavior preserved (400 for invalid types, 401 for unauthenticated)

Validation

  • npm run lint ✅
  • npm run typecheck ✅
  • npm run test (1335 tests) ✅
  • npm run build ✅

Closes#413
Closes#409

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#413Closes#409

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

// Authenticateif(!(awaitauthorizeRequest(request)).authorized){returnNextResponse.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 provided
  • returns 401 when token is incorrect
  • unauthorized 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

FileFindingSeverityCategory
route.tsAuth added before validation — correctly ordered
route.tsderiveStatus() maps outcomes to statuses: failed→failed, blocked→blocked, else→completed
route.tsresolveIssueId() does two DB lookups (repo, then issue) — graceful null on missing
route.tsbuildTouchedUrls() constructs GitHub URLs from report fields
route.tsAgentRun created only after all validation passes
route.tsResponse includes agentRunId: run.id for caller traceability
route.test.tsTest file restructured into logical describe blocks: auth, validation, persistence
route.test.tsAuth tests mock isAuthorizedAgentToken / isAuthorizedBearerToken to isolate behavior
route.test.tsNo findings — tests are comprehensive and correctly assert 401/400/200 paths

Standards Compliance

StandardStatusEvidence
Bearer token auth via DISPATCH_AGENT_TOKENauthorizeRequest(request) from @/lib/auth
Return 401 for unauthenticatedExplicit NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Return 400 for validation errorsPreserved from original implementation
No secrets in responsesTest: "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

IssueAcceptance CriteriaVerification
PR 409 (P1): Add authentication or remove endpointAdd authorizeRequest() call✅ Added; test confirms 401 for missing/invalid token
PR 413 (P2): Decide persistence model and add testsPersist 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:

  • authorizeRequest is imported from @/lib/auth — correct
  • authorizeRequest returns { authorized: boolean, ... } — route checks .authorized
  • src/lib/auth.ts confirms legacy mode (no DISPATCH_AUTH_MODE set) uses Bearer token checks — consistent with agent API contract

CI Check Results

CheckStatus
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.

@joryirving
joryirving merged commit 2cdda7b into mainJun 17, 2026
3 checks passed
@joryirving
joryirving deleted the feat/persist-agent-task-report branch June 17, 2026 15:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: persist authenticated agent task reports - #426

Merged
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report
Jun 17, 2026
Merged

feat: persist authenticated agent task reports#426
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Persist authenticated agent task reports as AgentRun rows.

Changes

  • Add Bearer token auth to POST /api/agents/[agentName]/tasks/report using existing authorizeRequest helper
  • Persist one AgentRun row per valid report:
    • agentName: from route param
    • runType: from taskType
    • status: derived from outcome (failedfailed, blockedblocked, everything else → completed)
    • startedAt / finishedAt: current timestamp
    • summary: report summary, if provided
    • errorMessage: report error, if provided
    • touchedIssueUrls: built from issue and PR references
    • issueId: resolved from repoFullName + issueNumber when matching Issue exists; otherwise null
  • Response includes { ok, agentName, report, agentRunId }
  • Validation failures and unauthorized requests return early without creating AgentRun
  • All existing validation behavior preserved (400 for invalid types, 401 for unauthenticated)

Validation

  • npm run lint ✅
  • npm run typecheck ✅
  • npm run test (1335 tests) ✅
  • npm run build ✅

Closes#413
Closes#409

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#413Closes#409

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

// Authenticateif(!(awaitauthorizeRequest(request)).authorized){returnNextResponse.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 provided
  • returns 401 when token is incorrect
  • unauthorized 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

FileFindingSeverityCategory
route.tsAuth added before validation — correctly ordered
route.tsderiveStatus() maps outcomes to statuses: failed→failed, blocked→blocked, else→completed
route.tsresolveIssueId() does two DB lookups (repo, then issue) — graceful null on missing
route.tsbuildTouchedUrls() constructs GitHub URLs from report fields
route.tsAgentRun created only after all validation passes
route.tsResponse includes agentRunId: run.id for caller traceability
route.test.tsTest file restructured into logical describe blocks: auth, validation, persistence
route.test.tsAuth tests mock isAuthorizedAgentToken / isAuthorizedBearerToken to isolate behavior
route.test.tsNo findings — tests are comprehensive and correctly assert 401/400/200 paths

Standards Compliance

StandardStatusEvidence
Bearer token auth via DISPATCH_AGENT_TOKENauthorizeRequest(request) from @/lib/auth
Return 401 for unauthenticatedExplicit NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Return 400 for validation errorsPreserved from original implementation
No secrets in responsesTest: "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

IssueAcceptance CriteriaVerification
PR 409 (P1): Add authentication or remove endpointAdd authorizeRequest() call✅ Added; test confirms 401 for missing/invalid token
PR 413 (P2): Decide persistence model and add testsPersist 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:

  • authorizeRequest is imported from @/lib/auth — correct
  • authorizeRequest returns { authorized: boolean, ... } — route checks .authorized
  • src/lib/auth.ts confirms legacy mode (no DISPATCH_AUTH_MODE set) uses Bearer token checks — consistent with agent API contract

CI Check Results

CheckStatus
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.

@joryirving
joryirving merged commit 2cdda7b into mainJun 17, 2026
3 checks passed
@joryirving
joryirving deleted the feat/persist-agent-task-report branch June 17, 2026 15:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: persist authenticated agent task reports - #426

Merged
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report
Jun 17, 2026
Merged

feat: persist authenticated agent task reports#426
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Persist authenticated agent task reports as AgentRun rows.

Changes

  • Add Bearer token auth to POST /api/agents/[agentName]/tasks/report using existing authorizeRequest helper
  • Persist one AgentRun row per valid report:
    • agentName: from route param
    • runType: from taskType
    • status: derived from outcome (failedfailed, blockedblocked, everything else → completed)
    • startedAt / finishedAt: current timestamp
    • summary: report summary, if provided
    • errorMessage: report error, if provided
    • touchedIssueUrls: built from issue and PR references
    • issueId: resolved from repoFullName + issueNumber when matching Issue exists; otherwise null
  • Response includes { ok, agentName, report, agentRunId }
  • Validation failures and unauthorized requests return early without creating AgentRun
  • All existing validation behavior preserved (400 for invalid types, 401 for unauthenticated)

Validation

  • npm run lint ✅
  • npm run typecheck ✅
  • npm run test (1335 tests) ✅
  • npm run build ✅

Closes#413
Closes#409

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#413Closes#409

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

// Authenticateif(!(awaitauthorizeRequest(request)).authorized){returnNextResponse.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 provided
  • returns 401 when token is incorrect
  • unauthorized 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

FileFindingSeverityCategory
route.tsAuth added before validation — correctly ordered
route.tsderiveStatus() maps outcomes to statuses: failed→failed, blocked→blocked, else→completed
route.tsresolveIssueId() does two DB lookups (repo, then issue) — graceful null on missing
route.tsbuildTouchedUrls() constructs GitHub URLs from report fields
route.tsAgentRun created only after all validation passes
route.tsResponse includes agentRunId: run.id for caller traceability
route.test.tsTest file restructured into logical describe blocks: auth, validation, persistence
route.test.tsAuth tests mock isAuthorizedAgentToken / isAuthorizedBearerToken to isolate behavior
route.test.tsNo findings — tests are comprehensive and correctly assert 401/400/200 paths

Standards Compliance

StandardStatusEvidence
Bearer token auth via DISPATCH_AGENT_TOKENauthorizeRequest(request) from @/lib/auth
Return 401 for unauthenticatedExplicit NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Return 400 for validation errorsPreserved from original implementation
No secrets in responsesTest: "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

IssueAcceptance CriteriaVerification
PR 409 (P1): Add authentication or remove endpointAdd authorizeRequest() call✅ Added; test confirms 401 for missing/invalid token
PR 413 (P2): Decide persistence model and add testsPersist 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:

  • authorizeRequest is imported from @/lib/auth — correct
  • authorizeRequest returns { authorized: boolean, ... } — route checks .authorized
  • src/lib/auth.ts confirms legacy mode (no DISPATCH_AUTH_MODE set) uses Bearer token checks — consistent with agent API contract

CI Check Results

CheckStatus
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.

@joryirving
joryirving merged commit 2cdda7b into mainJun 17, 2026
3 checks passed
@joryirving
joryirving deleted the feat/persist-agent-task-report branch June 17, 2026 15:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: persist authenticated agent task reports - #426

Merged
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report
Jun 17, 2026
Merged

feat: persist authenticated agent task reports#426
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Persist authenticated agent task reports as AgentRun rows.

Changes

  • Add Bearer token auth to POST /api/agents/[agentName]/tasks/report using existing authorizeRequest helper
  • Persist one AgentRun row per valid report:
    • agentName: from route param
    • runType: from taskType
    • status: derived from outcome (failedfailed, blockedblocked, everything else → completed)
    • startedAt / finishedAt: current timestamp
    • summary: report summary, if provided
    • errorMessage: report error, if provided
    • touchedIssueUrls: built from issue and PR references
    • issueId: resolved from repoFullName + issueNumber when matching Issue exists; otherwise null
  • Response includes { ok, agentName, report, agentRunId }
  • Validation failures and unauthorized requests return early without creating AgentRun
  • All existing validation behavior preserved (400 for invalid types, 401 for unauthenticated)

Validation

  • npm run lint ✅
  • npm run typecheck ✅
  • npm run test (1335 tests) ✅
  • npm run build ✅

Closes#413
Closes#409

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#413Closes#409

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

// Authenticateif(!(awaitauthorizeRequest(request)).authorized){returnNextResponse.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 provided
  • returns 401 when token is incorrect
  • unauthorized 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

FileFindingSeverityCategory
route.tsAuth added before validation — correctly ordered
route.tsderiveStatus() maps outcomes to statuses: failed→failed, blocked→blocked, else→completed
route.tsresolveIssueId() does two DB lookups (repo, then issue) — graceful null on missing
route.tsbuildTouchedUrls() constructs GitHub URLs from report fields
route.tsAgentRun created only after all validation passes
route.tsResponse includes agentRunId: run.id for caller traceability
route.test.tsTest file restructured into logical describe blocks: auth, validation, persistence
route.test.tsAuth tests mock isAuthorizedAgentToken / isAuthorizedBearerToken to isolate behavior
route.test.tsNo findings — tests are comprehensive and correctly assert 401/400/200 paths

Standards Compliance

StandardStatusEvidence
Bearer token auth via DISPATCH_AGENT_TOKENauthorizeRequest(request) from @/lib/auth
Return 401 for unauthenticatedExplicit NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Return 400 for validation errorsPreserved from original implementation
No secrets in responsesTest: "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

IssueAcceptance CriteriaVerification
PR 409 (P1): Add authentication or remove endpointAdd authorizeRequest() call✅ Added; test confirms 401 for missing/invalid token
PR 413 (P2): Decide persistence model and add testsPersist 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:

  • authorizeRequest is imported from @/lib/auth — correct
  • authorizeRequest returns { authorized: boolean, ... } — route checks .authorized
  • src/lib/auth.ts confirms legacy mode (no DISPATCH_AUTH_MODE set) uses Bearer token checks — consistent with agent API contract

CI Check Results

CheckStatus
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.

@joryirving
joryirving merged commit 2cdda7b into mainJun 17, 2026
3 checks passed
@joryirving
joryirving deleted the feat/persist-agent-task-report branch June 17, 2026 15:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: persist authenticated agent task reports - #426

Merged
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report
Jun 17, 2026
Merged

feat: persist authenticated agent task reports#426
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Persist authenticated agent task reports as AgentRun rows.

Changes

  • Add Bearer token auth to POST /api/agents/[agentName]/tasks/report using existing authorizeRequest helper
  • Persist one AgentRun row per valid report:
    • agentName: from route param
    • runType: from taskType
    • status: derived from outcome (failedfailed, blockedblocked, everything else → completed)
    • startedAt / finishedAt: current timestamp
    • summary: report summary, if provided
    • errorMessage: report error, if provided
    • touchedIssueUrls: built from issue and PR references
    • issueId: resolved from repoFullName + issueNumber when matching Issue exists; otherwise null
  • Response includes { ok, agentName, report, agentRunId }
  • Validation failures and unauthorized requests return early without creating AgentRun
  • All existing validation behavior preserved (400 for invalid types, 401 for unauthenticated)

Validation

  • npm run lint ✅
  • npm run typecheck ✅
  • npm run test (1335 tests) ✅
  • npm run build ✅

Closes#413
Closes#409

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#413Closes#409

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

// Authenticateif(!(awaitauthorizeRequest(request)).authorized){returnNextResponse.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 provided
  • returns 401 when token is incorrect
  • unauthorized 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

FileFindingSeverityCategory
route.tsAuth added before validation — correctly ordered
route.tsderiveStatus() maps outcomes to statuses: failed→failed, blocked→blocked, else→completed
route.tsresolveIssueId() does two DB lookups (repo, then issue) — graceful null on missing
route.tsbuildTouchedUrls() constructs GitHub URLs from report fields
route.tsAgentRun created only after all validation passes
route.tsResponse includes agentRunId: run.id for caller traceability
route.test.tsTest file restructured into logical describe blocks: auth, validation, persistence
route.test.tsAuth tests mock isAuthorizedAgentToken / isAuthorizedBearerToken to isolate behavior
route.test.tsNo findings — tests are comprehensive and correctly assert 401/400/200 paths

Standards Compliance

StandardStatusEvidence
Bearer token auth via DISPATCH_AGENT_TOKENauthorizeRequest(request) from @/lib/auth
Return 401 for unauthenticatedExplicit NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Return 400 for validation errorsPreserved from original implementation
No secrets in responsesTest: "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

IssueAcceptance CriteriaVerification
PR 409 (P1): Add authentication or remove endpointAdd authorizeRequest() call✅ Added; test confirms 401 for missing/invalid token
PR 413 (P2): Decide persistence model and add testsPersist 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:

  • authorizeRequest is imported from @/lib/auth — correct
  • authorizeRequest returns { authorized: boolean, ... } — route checks .authorized
  • src/lib/auth.ts confirms legacy mode (no DISPATCH_AUTH_MODE set) uses Bearer token checks — consistent with agent API contract

CI Check Results

CheckStatus
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.

@joryirving
joryirving merged commit 2cdda7b into mainJun 17, 2026
3 checks passed
@joryirving
joryirving deleted the feat/persist-agent-task-report branch June 17, 2026 15:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@joryirving
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: persist authenticated agent task reports - #426

Merged
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report
Jun 17, 2026
Merged

feat: persist authenticated agent task reports#426
joryirving merged 1 commit into
mainfrom
feat/persist-agent-task-report

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Persist authenticated agent task reports as AgentRun rows.

Changes

  • Add Bearer token auth to POST /api/agents/[agentName]/tasks/report using existing authorizeRequest helper
  • Persist one AgentRun row per valid report:
    • agentName: from route param
    • runType: from taskType
    • status: derived from outcome (failedfailed, blockedblocked, everything else → completed)
    • startedAt / finishedAt: current timestamp
    • summary: report summary, if provided
    • errorMessage: report error, if provided
    • touchedIssueUrls: built from issue and PR references
    • issueId: resolved from repoFullName + issueNumber when matching Issue exists; otherwise null
  • Response includes { ok, agentName, report, agentRunId }
  • Validation failures and unauthorized requests return early without creating AgentRun
  • All existing validation behavior preserved (400 for invalid types, 401 for unauthenticated)

Validation

  • npm run lint ✅
  • npm run typecheck ✅
  • npm run test (1335 tests) ✅
  • npm run build ✅

Closes#413
Closes#409

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#413Closes#409

@its-saffronits-saffronBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

// Authenticateif(!(awaitauthorizeRequest(request)).authorized){returnNextResponse.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 provided
  • returns 401 when token is incorrect
  • unauthorized 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

FileFindingSeverityCategory
route.tsAuth added before validation — correctly ordered
route.tsderiveStatus() maps outcomes to statuses: failed→failed, blocked→blocked, else→completed
route.tsresolveIssueId() does two DB lookups (repo, then issue) — graceful null on missing
route.tsbuildTouchedUrls() constructs GitHub URLs from report fields
route.tsAgentRun created only after all validation passes
route.tsResponse includes agentRunId: run.id for caller traceability
route.test.tsTest file restructured into logical describe blocks: auth, validation, persistence
route.test.tsAuth tests mock isAuthorizedAgentToken / isAuthorizedBearerToken to isolate behavior
route.test.tsNo findings — tests are comprehensive and correctly assert 401/400/200 paths

Standards Compliance

StandardStatusEvidence
Bearer token auth via DISPATCH_AGENT_TOKENauthorizeRequest(request) from @/lib/auth
Return 401 for unauthenticatedExplicit NextResponse.json({ error: "Unauthorized" }, { status: 401 })
Return 400 for validation errorsPreserved from original implementation
No secrets in responsesTest: "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

IssueAcceptance CriteriaVerification
PR 409 (P1): Add authentication or remove endpointAdd authorizeRequest() call✅ Added; test confirms 401 for missing/invalid token
PR 413 (P2): Decide persistence model and add testsPersist 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:

  • authorizeRequest is imported from @/lib/auth — correct
  • authorizeRequest returns { authorized: boolean, ... } — route checks .authorized
  • src/lib/auth.ts confirms legacy mode (no DISPATCH_AUTH_MODE set) uses Bearer token checks — consistent with agent API contract

CI Check Results

CheckStatus
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.

@joryirving
joryirving merged commit 2cdda7b into mainJun 17, 2026
3 checks passed
@joryirving
joryirving deleted the feat/persist-agent-task-report branch June 17, 2026 15:53
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant

@joryirving