feat: agent-day task — autonomous Sunday feature implementation#108
feat: agent-day task — autonomous Sunday feature implementation#108sweetmantech wants to merge 4 commits intomainfrom
Conversation
Adds a new `agent-day` scheduled task that runs every Sunday at 10 AM ET and implements a new feature end-to-end without human intervention: 1. Fetches recent commits from all 11 submodule repos via GitHub API 2. Uses Claude (Anthropic API) to plan the next most valuable feature 3. Triggers `coding-agent` to implement it and open PRs 4. Reviews each PR: waits for CI checks, assesses review comments with Claude 5. Applies valid feedback via `update-pr` task (up to 3 iterations per PR) 6. Tests Vercel preview deployments (health check for api/chat repos) 7. Merges approved PRs via GitHub API (squash merge) 8. Posts a summary to Slack channel #C08HN8RKJHZ New utilities: - src/github/fetchRecentSubmoduleCommits.ts - src/github/waitForPRChecks.ts - src/github/fetchPRReviews.ts - src/github/mergePR.ts - src/github/getVercelPreviewUrl.ts - src/slack/postToSlackChannel.ts - src/ai/generateFeaturePrompt.ts - src/ai/assessPRFeedback.ts Schema changes (backward compatible): - codingAgentSchema: callbackThreadId is now optional - updatePRSchema: callbackThreadId is now optional Both coding-agent and update-pr tasks skip the Slack callback when callbackThreadId is not provided (used when triggered programmatically). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds AI-assisted modules, GitHub integrations, Slack messaging, schemas tweaks, tests, and a scheduled agentDayTask to implement features, iterate on PRs using AI sandbox assessments, run CI/preview checks, and attempt merges; several tasks now conditionally notify a coding-agent callback only when a callbackThreadId is present. Changes
Sequence DiagramsequenceDiagram
participant Scheduler as Scheduler (Trigger.dev)
participant AgentTask as agentDayTask
participant Sandbox as AI Sandbox
participant GitHub as GitHub API
participant Slack as Slack API
Scheduler->>AgentTask: scheduled run (Sun 10am ET)
AgentTask->>Sandbox: getOrCreateSandbox()
AgentTask->>GitHub: fetchRecentSubmoduleCommits()
GitHub-->>AgentTask: commits
AgentTask->>Sandbox: generateFeaturePrompt(commits)
Sandbox-->>AgentTask: feature prompt
AgentTask->>Sandbox: codingAgentTask.triggerAndWait(prompt)
alt coding agent succeeds
AgentTask->>GitHub: create PRs (via coding agent)
GitHub-->>AgentTask: PR list
loop per PR
AgentTask->>GitHub: waitForPRChecks(pr)
GitHub-->>AgentTask: checks status
loop review iterations
AgentTask->>GitHub: fetchPRReviews(pr)
GitHub-->>AgentTask: reviews & comments
AgentTask->>Sandbox: assessPRFeedback(reviews)
alt actionable feedback
AgentTask->>AgentTask: updatePRTask.triggerAndWait(...)
end
end
AgentTask->>GitHub: getVercelPreviewUrl(pr)
GitHub-->>AgentTask: preview URL / null
alt preview exists
AgentTask->>GitHub: health check (preview/api/health)
end
AgentTask->>GitHub: mergePR(pr)
GitHub-->>AgentTask: merge result
end
AgentTask->>Slack: postToSlackChannel(summary)
else coding agent fails
AgentTask->>Slack: postToSlackChannel(error)
end
AgentTask->>Sandbox: stop()
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
src/ai/assessPRFeedback.ts
Outdated
| const feedbackText = [reviewsText, commentsText].filter(Boolean).join("\n\n"); | ||
|
|
||
| try { | ||
| const response = await fetch("https://api.anthropic.com/v1/messages", { |
There was a problem hiding this comment.
use claudeCode in a sandbox instead of anthropic api calls.
src/ai/generateFeaturePrompt.ts
Outdated
| Based on this recent work, write a specific implementation prompt for an AI coding agent to implement the next most valuable feature.`; | ||
|
|
||
| try { | ||
| const response = await fetch("https://api.anthropic.com/v1/messages", { |
There was a problem hiding this comment.
use claudeCode in a sandbox instead of anthropic api calls. Use existing claude code sandbox libs.
src/github/waitForPRChecks.ts
Outdated
| }; | ||
| } | ||
|
|
||
| logger.log(`Waiting for ${pending.length} checks on PR #${prNumber}`, { |
There was a problem hiding this comment.
replace any usage of logger.log with the shared logStep function lib.
src/slack/postToSlackChannel.ts
Outdated
| return false; | ||
| } | ||
|
|
||
| logger.log("Posted message to Slack", { channel: channelId }); |
There was a problem hiding this comment.
replace all logger.log with shared logStep function.
…g with logStep Addresses PR review feedback: - Replace raw Anthropic API calls in assessPRFeedback and generateFeaturePrompt with runClaudeCodeAgent in a Vercel sandbox - Replace logger.log with shared logStep function in waitForPRChecks and postToSlackChannel - Create dedicated AI reasoning sandbox in agentDayTask with proper cleanup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (6)
src/github/mergePR.ts (1)
13-26: Consider wrappingfetchin try-catch for network failures.Similar to
postToSlackChannel, network-level errors (DNS, timeouts) will propagate uncaught. For consistency and resilience, consider catching and returningfalse.♻️ Proposed fix to handle network errors
+ let response: Response; + try { + response = await fetch( + `https://api.github.com/repos/${repo}/pulls/${prNumber}/merge`, + { + method: "PUT", + headers: { + Authorization: `token ${token}`, + Accept: "application/vnd.github.v3+json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + merge_method: "squash", + }), + }, + ); + } catch (err) { + logger.error(`Network error merging PR #${prNumber} in ${repo}`, { error: String(err) }); + return false; + } - const response = await fetch( - `https://api.github.com/repos/${repo}/pulls/${prNumber}/merge`, - { - method: "PUT", - headers: { - Authorization: `token ${token}`, - Accept: "application/vnd.github.v3+json", - "Content-Type": "application/json", - }, - body: JSON.stringify({ - merge_method: "squash", - }), - }, - );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/github/mergePR.ts` around lines 13 - 26, The fetch call in mergePR (the request to `https://api.github.com/repos/${repo}/pulls/${prNumber}/merge`) can throw network-level errors; wrap the await fetch in a try-catch inside the mergePR function (or the surrounding async function) and on catch return false (matching postToSlackChannel behavior), logging or preserving the error as appropriate; ensure the existing response handling remains inside the try block and only returns true/false after successful fetch/response processing.src/github/fetchRecentSubmoduleCommits.ts (1)
18-53: Consider parallel fetching for improved performance.Sequential fetching across 11 submodules introduces latency. Since these are independent API calls (not Trigger.dev
waitortriggerAndWaitcalls), they can safely be parallelized withPromise.allSettledwhile still handling individual failures gracefully.⚡ Proposed optimization using parallel fetching
export async function fetchRecentSubmoduleCommits(): Promise<SubmoduleCommit[]> { const token = process.env.GITHUB_TOKEN; - const results: SubmoduleCommit[] = []; + const entries = Object.entries(SUBMODULE_CONFIG); - for (const [submodule, { repo }] of Object.entries(SUBMODULE_CONFIG)) { - try { - const response = await fetch( + const fetchPromises = entries.map(async ([submodule, { repo }]) => { + try { + const response = await fetch( `https://api.github.com/repos/${repo}/commits?per_page=5`, { headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json", }, }, ); if (!response.ok) { logger.warn(`Failed to fetch commits for ${repo}`, { status: response.status }); - continue; + return null; } const data = (await response.json()) as Array<{ sha: string; commit: { message: string; author: { name: string; date: string } }; }>; - results.push({ + return { submodule, repo, commits: data.map((c) => ({ sha: c.sha.slice(0, 7), message: c.commit.message.split("\n")[0], author: c.commit.author.name, date: c.commit.author.date, })), - }); + } as SubmoduleCommit; } catch (error) { logger.warn(`Error fetching commits for ${repo}`, { error }); + return null; } - } + }); - return results; + const results = await Promise.all(fetchPromises); + return results.filter((r): r is SubmoduleCommit => r !== null); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/github/fetchRecentSubmoduleCommits.ts` around lines 18 - 53, The current loop in fetchRecentSubmoduleCommits iterates sequentially over SUBMODULE_CONFIG causing high latency; refactor it to perform the per-repo fetches in parallel by mapping Object.entries(SUBMODULE_CONFIG) to an array of fetch promises and using Promise.allSettled to wait for all; for each settled result, if fulfilled validate response.ok (log via logger.warn with repo and status on non-ok), parse JSON safely and push the transformed commit objects (sha slice, first-line message, author, date) into results, and if rejected log the error with logger.warn including the repo and error details so individual failures don't abort the whole operation.src/github/getVercelPreviewUrl.ts (1)
21-53: Consider wrapping fetch calls in try-catch for network error resilience.The function returns
nullon non-OK responses but doesn't handle network exceptions fromfetch(). If a network error occurs, the exception will propagate up to the caller.🛡️ Proposed fix to handle network errors
export async function getVercelPreviewUrl( repo: string, prNumber: number, ): Promise<string | null> { const token = process.env.GITHUB_TOKEN; const headers = { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json", }; + try { const prRes = await fetch(`https://api.github.com/repos/${repo}/pulls/${prNumber}`, { headers, }); if (!prRes.ok) return null; const pr = (await prRes.json()) as { head: { sha: string } }; const checksRes = await fetch( `https://api.github.com/repos/${repo}/commits/${pr.head.sha}/check-runs`, { headers }, ); if (!checksRes.ok) return null; // ... rest of the function + } catch (error) { + logger.warn(`Error fetching Vercel preview URL for PR #${prNumber}`, { error }); + return null; + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/github/getVercelPreviewUrl.ts` around lines 21 - 53, The fetch calls in getVercelPreviewUrl (calls that produce prRes, checksRes, statusRes, etc.) can throw on network errors but currently only check response.ok; wrap the sequence of fetches in a try-catch around the network interactions (or at least each await fetch) and return null (or log and return null) on caught exceptions so network failures don't propagate; ensure you catch errors thrown by fetch/response.json and apply the same null-return behavior the function already uses for non-OK responses.src/tasks/agentDayTask.ts (2)
46-50: Inconsistent indentation inside try block.The code starting at line 47 should be indented one additional level within the
tryblock. This appears to be a formatting issue.♻️ Fix indentation
try { - // Step 1: Gather recent commits to understand what has been built recently - logStep("Fetching recent commits from all submodules"); - const recentCommits = await fetchRecentSubmoduleCommits(); + // Step 1: Gather recent commits to understand what has been built recently + logStep("Fetching recent commits from all submodules"); + const recentCommits = await fetchRecentSubmoduleCommits();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/tasks/agentDayTask.ts` around lines 46 - 50, The code inside the try block in agentDayTask.ts is mis-indented; adjust the indentation of the lines starting with logStep("Fetching recent commits from all submodules"), the await fetchRecentSubmoduleCommits() call, and the subsequent logStep("Recent commits fetched", ...) so they are one additional level deeper (inside the try block) to match block scope; ensure the statements around logStep and fetchRecentSubmoduleCommits() are consistently indented and aligned with other try/catch blocks or surrounding code.
144-153: Health check result is logged but not acted upon.The Vercel preview health check captures the response status but doesn't fail the workflow or skip merge when the health endpoint returns an error status. If the preview is unhealthy, the PR will still be merged.
♻️ Proposed fix to skip merge on unhealthy preview
+ let previewHealthy = true; try { const healthRes = await fetch(`${previewUrl}/api/health`, { signal: AbortSignal.timeout(10_000), }); logStep(`Preview health check for PR #${pr.number}`, true, { url: `${previewUrl}/api/health`, status: healthRes.status, }); + if (!healthRes.ok) { + logger.warn(`Unhealthy preview for PR #${pr.number}`, { status: healthRes.status }); + previewHealthy = false; + } } catch (error) { logger.warn(`Preview health check failed for PR #${pr.number}`, { error }); + previewHealthy = false; } + + if (!previewHealthy) { + logger.warn(`Skipping merge for PR #${pr.number} due to unhealthy preview`); + continue; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/tasks/agentDayTask.ts` around lines 144 - 153, The health-check fetch in agentDayTask.ts currently only logs the response but doesn't stop processing on non-2xx responses; after awaiting fetch (healthRes) check healthRes.ok (or healthRes.status) and, if not healthy, throw or set a "previewUnhealthy" error/flag so the caller can skip merge; update the try block that calls fetch(`${previewUrl}/api/health`, ...) to treat non-ok responses as failures (use existing logStep/logger.warn for context) and ensure the surrounding flow (merge decision) observes that thrown error/flag to prevent merging an unhealthy preview.src/ai/assessPRFeedback.ts (1)
77-88: Consider validating parsed JSON with Zod.The type assertion
as FeedbackAssessmentdoesn't guarantee the parsed object has the required fields. Malformed AI output could lead toundefinedvalues propagating downstream.♻️ Proposed Zod validation
+import { z } from "zod"; + +const FeedbackAssessmentSchema = z.object({ + hasActionableFeedback: z.boolean(), + feedbackSummary: z.string(), + implementation: z.string(), +}); + // In the try block: try { - const parsed = JSON.parse(text) as FeedbackAssessment; + const parsed = FeedbackAssessmentSchema.parse(JSON.parse(text)); logStep("PR feedback assessment complete", false, {As per coding guidelines: "Use Zod for schema validation" applies to
src/**/*.{ts,tsx}files.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ai/assessPRFeedback.ts` around lines 77 - 88, Replace the unchecked JSON assertion with Zod validation: define a Zod schema for FeedbackAssessment (fields like hasActionableFeedback and feedbackSummary), parse the raw text with JSON.parse into a temp value then run FeedbackAssessmentSchema.safeParse(temp) inside the try; if safeParse succeeds return the validated data, otherwise log the validation errors via logStep (include the sliced text and the safeParse.error) and return noFeedback. Update the block using the symbols parsed, FeedbackAssessment, FeedbackAssessmentSchema (or similar), logStep, and noFeedback so downstream code gets a fully validated object.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/github/fetchPRReviews.ts`:
- Around line 71-77: The map that pushes PR review comments accesses
c.user.login without guarding against deleted/null GitHub accounts; update the
mapping in the comments.push(...data.map(...)) call to safely handle missing
c.user (e.g., use c.user?.login or a fallback like "deleted" or null) and ensure
other fields that may be absent are similarly guarded (author should be derived
from c.user?.login, leave body/path/createdAt as before).
- Around line 50-58: The mapping currently dereferences r.user.login which can
be null for deleted accounts; update the data processing so you first guard for
a present user (e.g., in the filter used before map) or provide a safe fallback
for the author field to avoid a crash. Specifically adjust the pipeline around
reviews.push and the data.filter/map (the anonymous arrow functions that
reference r.user.login and r.body) so the filter excludes entries with no r.user
or uses r.user?.login (or a fallback like "deleted" / "unknown") for author
while preserving body, state, and submitted_at extraction.
- Around line 29-33: Validate that process.env.GITHUB_TOKEN is set before
constructing headers in fetchPRReviews (token variable); if it's missing, throw
an explicit Error or exit with a clear message (e.g., "GITHUB_TOKEN is
required") so the Authorization header isn't built as "token undefined" and
authentication fails fast. Ensure the check occurs before creating the headers
object and reference the token constant used to build Authorization.
In `@src/github/mergePR.ts`:
- Around line 10-12: The mergePR function uses process.env.GITHUB_TOKEN without
checking it; add an early validation at the start of mergePR to detect a missing
or empty GITHUB_TOKEN and fail fast with a clear error (either throw an Error
with a descriptive message or log and return false) before constructing the
Authorization header; ensure the check references mergePR and prevents sending
"Authorization: token undefined" by exiting the function if the token is absent.
In `@src/github/waitForPRChecks.ts`:
- Around line 60-88: When checksData.check_runs is empty the polling loop never
exits; update the logic in waitForPRChecks (the loop that reads checks and
computes pending/failed using the checks variable) to detect checks.length === 0
and return early (e.g., log that no checks exist and return { allPassed: true,
failedChecks: [], pendingChecks: [] }) so repositories without CI don't poll
until timeout.
In `@src/slack/postToSlackChannel.ts`:
- Around line 19-26: postToSlackChannel should guard the fetch call against
network-level failures by wrapping the await fetch(...) in a try-catch inside
the postToSlackChannel function; on catch, log the caught error (including
error.message) and return false so the function consistently returns a boolean
for network errors, while preserving existing handling of API responses
(response.ok and parsed body). Locate the fetch invocation and the subsequent
response handling (variables named response and the JSON parsing) and move the
fetch+response parsing into the try block and return false from the catch after
logging contextual info (channelId/token context as appropriate).
In `@src/tasks/agentDayTask.ts`:
- Around line 88-94: The PR merge is proceeding regardless of CI results: use
the result returned by waitForPRChecks (checkResult) to gate merging—if
checkResult.allPassed is false, abort the merge flow, log an error via logStep
including checkResult.failedChecks, and return or throw to stop further actions;
only proceed to the existing merge logic when checkResult.allPassed is true
(reference waitForPRChecks, checkResult, logStep, PR_CHECK_TIMEOUT_MS).
- Around line 99-134: After a successful updatePRTask.triggerAndWait, call and
await a waitForPRChecks function (e.g., waitForPRChecks(pr.repo, pr.number,
currentSnapshotId)) before continuing the review loop; if waitForPRChecks
returns a non-passing state (timeout/failure), log a warning with details (use
logger.warn) and break the loop to avoid proceeding to merge on failing CI,
otherwise continue as before, ensuring the call is placed after updateResult.ok
handling and before the next iteration’s reassessment (replace or augment the
existing wait.for({ seconds: 30 }) with this check so CI status is validated).
---
Nitpick comments:
In `@src/ai/assessPRFeedback.ts`:
- Around line 77-88: Replace the unchecked JSON assertion with Zod validation:
define a Zod schema for FeedbackAssessment (fields like hasActionableFeedback
and feedbackSummary), parse the raw text with JSON.parse into a temp value then
run FeedbackAssessmentSchema.safeParse(temp) inside the try; if safeParse
succeeds return the validated data, otherwise log the validation errors via
logStep (include the sliced text and the safeParse.error) and return noFeedback.
Update the block using the symbols parsed, FeedbackAssessment,
FeedbackAssessmentSchema (or similar), logStep, and noFeedback so downstream
code gets a fully validated object.
In `@src/github/fetchRecentSubmoduleCommits.ts`:
- Around line 18-53: The current loop in fetchRecentSubmoduleCommits iterates
sequentially over SUBMODULE_CONFIG causing high latency; refactor it to perform
the per-repo fetches in parallel by mapping Object.entries(SUBMODULE_CONFIG) to
an array of fetch promises and using Promise.allSettled to wait for all; for
each settled result, if fulfilled validate response.ok (log via logger.warn with
repo and status on non-ok), parse JSON safely and push the transformed commit
objects (sha slice, first-line message, author, date) into results, and if
rejected log the error with logger.warn including the repo and error details so
individual failures don't abort the whole operation.
In `@src/github/getVercelPreviewUrl.ts`:
- Around line 21-53: The fetch calls in getVercelPreviewUrl (calls that produce
prRes, checksRes, statusRes, etc.) can throw on network errors but currently
only check response.ok; wrap the sequence of fetches in a try-catch around the
network interactions (or at least each await fetch) and return null (or log and
return null) on caught exceptions so network failures don't propagate; ensure
you catch errors thrown by fetch/response.json and apply the same null-return
behavior the function already uses for non-OK responses.
In `@src/github/mergePR.ts`:
- Around line 13-26: The fetch call in mergePR (the request to
`https://api.github.com/repos/${repo}/pulls/${prNumber}/merge`) can throw
network-level errors; wrap the await fetch in a try-catch inside the mergePR
function (or the surrounding async function) and on catch return false (matching
postToSlackChannel behavior), logging or preserving the error as appropriate;
ensure the existing response handling remains inside the try block and only
returns true/false after successful fetch/response processing.
In `@src/tasks/agentDayTask.ts`:
- Around line 46-50: The code inside the try block in agentDayTask.ts is
mis-indented; adjust the indentation of the lines starting with
logStep("Fetching recent commits from all submodules"), the await
fetchRecentSubmoduleCommits() call, and the subsequent logStep("Recent commits
fetched", ...) so they are one additional level deeper (inside the try block) to
match block scope; ensure the statements around logStep and
fetchRecentSubmoduleCommits() are consistently indented and aligned with other
try/catch blocks or surrounding code.
- Around line 144-153: The health-check fetch in agentDayTask.ts currently only
logs the response but doesn't stop processing on non-2xx responses; after
awaiting fetch (healthRes) check healthRes.ok (or healthRes.status) and, if not
healthy, throw or set a "previewUnhealthy" error/flag so the caller can skip
merge; update the try block that calls fetch(`${previewUrl}/api/health`, ...) to
treat non-ok responses as failures (use existing logStep/logger.warn for
context) and ensure the surrounding flow (merge decision) observes that thrown
error/flag to prevent merging an unhealthy preview.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d8c9c5fc-3347-493c-ac19-b3af66ebd2a2
📒 Files selected for processing (19)
src/ai/__tests__/assessPRFeedback.test.tssrc/ai/__tests__/generateFeaturePrompt.test.tssrc/ai/assessPRFeedback.tssrc/ai/generateFeaturePrompt.tssrc/github/__tests__/fetchPRReviews.test.tssrc/github/__tests__/fetchRecentSubmoduleCommits.test.tssrc/github/__tests__/mergePR.test.tssrc/github/fetchPRReviews.tssrc/github/fetchRecentSubmoduleCommits.tssrc/github/getVercelPreviewUrl.tssrc/github/mergePR.tssrc/github/waitForPRChecks.tssrc/schemas/codingAgentSchema.tssrc/schemas/updatePRSchema.tssrc/slack/__tests__/postToSlackChannel.test.tssrc/slack/postToSlackChannel.tssrc/tasks/agentDayTask.tssrc/tasks/codingAgentTask.tssrc/tasks/updatePRTask.ts
| const token = process.env.GITHUB_TOKEN; | ||
| const headers = { | ||
| Authorization: `token ${token}`, | ||
| Accept: "application/vnd.github.v3+json", | ||
| }; |
There was a problem hiding this comment.
Missing validation for GITHUB_TOKEN.
If GITHUB_TOKEN is undefined, the Authorization header becomes "token undefined", which will silently fail authentication rather than failing fast with a clear error.
🛡️ Proposed fix to validate token
export async function fetchPRReviews(repo: string, prNumber: number): Promise<PRFeedback> {
const token = process.env.GITHUB_TOKEN;
+ if (!token) {
+ logger.warn("GITHUB_TOKEN not set, cannot fetch PR reviews");
+ return { reviews: [], comments: [] };
+ }
const headers = {
Authorization: `token ${token}`,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/github/fetchPRReviews.ts` around lines 29 - 33, Validate that
process.env.GITHUB_TOKEN is set before constructing headers in fetchPRReviews
(token variable); if it's missing, throw an explicit Error or exit with a clear
message (e.g., "GITHUB_TOKEN is required") so the Authorization header isn't
built as "token undefined" and authentication fails fast. Ensure the check
occurs before creating the headers object and reference the token constant used
to build Authorization.
| reviews.push( | ||
| ...data | ||
| .filter((r) => r.body?.trim()) | ||
| .map((r) => ({ | ||
| author: r.user.login, | ||
| body: r.body, | ||
| state: r.state, | ||
| submittedAt: r.submitted_at, | ||
| })), |
There was a problem hiding this comment.
Potential null dereference on r.user.
GitHub API can return null for user when the account has been deleted or for certain bot actions. Accessing r.user.login would throw if user is null.
🛡️ Proposed fix to guard against null user
reviews.push(
...data
- .filter((r) => r.body?.trim())
+ .filter((r) => r.body?.trim() && r.user)
.map((r) => ({
author: r.user.login,
body: r.body,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| reviews.push( | |
| ...data | |
| .filter((r) => r.body?.trim()) | |
| .map((r) => ({ | |
| author: r.user.login, | |
| body: r.body, | |
| state: r.state, | |
| submittedAt: r.submitted_at, | |
| })), | |
| reviews.push( | |
| ...data | |
| .filter((r) => r.body?.trim() && r.user) | |
| .map((r) => ({ | |
| author: r.user.login, | |
| body: r.body, | |
| state: r.state, | |
| submittedAt: r.submitted_at, | |
| })), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/github/fetchPRReviews.ts` around lines 50 - 58, The mapping currently
dereferences r.user.login which can be null for deleted accounts; update the
data processing so you first guard for a present user (e.g., in the filter used
before map) or provide a safe fallback for the author field to avoid a crash.
Specifically adjust the pipeline around reviews.push and the data.filter/map
(the anonymous arrow functions that reference r.user.login and r.body) so the
filter excludes entries with no r.user or uses r.user?.login (or a fallback like
"deleted" / "unknown") for author while preserving body, state, and submitted_at
extraction.
| comments.push( | ||
| ...data.map((c) => ({ | ||
| author: c.user.login, | ||
| body: c.body, | ||
| path: c.path, | ||
| createdAt: c.created_at, | ||
| })), |
There was a problem hiding this comment.
Same null user concern for comments.
Apply the same guard for c.user to prevent runtime errors on deleted accounts.
🛡️ Proposed fix
comments.push(
- ...data.map((c) => ({
+ ...data
+ .filter((c) => c.user)
+ .map((c) => ({
author: c.user.login,
body: c.body,
path: c.path,
createdAt: c.created_at,
})),
);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/github/fetchPRReviews.ts` around lines 71 - 77, The map that pushes PR
review comments accesses c.user.login without guarding against deleted/null
GitHub accounts; update the mapping in the comments.push(...data.map(...)) call
to safely handle missing c.user (e.g., use c.user?.login or a fallback like
"deleted" or null) and ensure other fields that may be absent are similarly
guarded (author should be derived from c.user?.login, leave body/path/createdAt
as before).
| export async function mergePR(repo: string, prNumber: number): Promise<boolean> { | ||
| const token = process.env.GITHUB_TOKEN; | ||
|
|
There was a problem hiding this comment.
Add validation for missing GITHUB_TOKEN.
If GITHUB_TOKEN is undefined, the request sends Authorization: token undefined, which will fail with a 401 but produces a confusing error. Early validation provides a clearer failure mode.
🛡️ Proposed fix to validate token
export async function mergePR(repo: string, prNumber: number): Promise<boolean> {
const token = process.env.GITHUB_TOKEN;
+
+ if (!token) {
+ logger.error("Missing GITHUB_TOKEN — cannot merge PR");
+ return false;
+ }
const response = await fetch(📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function mergePR(repo: string, prNumber: number): Promise<boolean> { | |
| const token = process.env.GITHUB_TOKEN; | |
| export async function mergePR(repo: string, prNumber: number): Promise<boolean> { | |
| const token = process.env.GITHUB_TOKEN; | |
| if (!token) { | |
| logger.error("Missing GITHUB_TOKEN — cannot merge PR"); | |
| return false; | |
| } | |
| const response = await fetch( |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/github/mergePR.ts` around lines 10 - 12, The mergePR function uses
process.env.GITHUB_TOKEN without checking it; add an early validation at the
start of mergePR to detect a missing or empty GITHUB_TOKEN and fail fast with a
clear error (either throw an Error with a descriptive message or log and return
false) before constructing the Authorization header; ensure the check references
mergePR and prevents sending "Authorization: token undefined" by exiting the
function if the token is absent.
| if (checks.length > 0) { | ||
| const pending = checks.filter((c) => c.status !== "completed"); | ||
| const failed = checks.filter( | ||
| (c) => | ||
| c.status === "completed" && | ||
| c.conclusion !== "success" && | ||
| c.conclusion !== "skipped" && | ||
| c.conclusion !== "neutral", | ||
| ); | ||
|
|
||
| if (pending.length === 0) { | ||
| logStep(`All checks complete for PR #${prNumber}`, false, { | ||
| total: checks.length, | ||
| failed: failed.length, | ||
| }); | ||
| return { | ||
| allPassed: failed.length === 0, | ||
| failedChecks: failed.map((c) => c.name), | ||
| pendingChecks: [], | ||
| }; | ||
| } | ||
|
|
||
| logStep(`Waiting for ${pending.length} checks on PR #${prNumber}`, false, { | ||
| pending: pending.map((c) => c.name), | ||
| }); | ||
| } | ||
|
|
||
| await wait.for({ seconds: 30 }); | ||
| } |
There was a problem hiding this comment.
Handle the case when no check runs exist for the commit.
If a repository has no CI checks configured, checksData.check_runs will be empty and the loop will poll until timeout. Consider returning early when no checks exist.
🐛 Proposed fix to handle empty check runs
const checks = checksData.check_runs;
+ // If no checks are configured, consider it passed
+ if (checks.length === 0) {
+ logStep(`No check runs found for PR #${prNumber}`, false);
+ return { allPassed: true, failedChecks: [], pendingChecks: [] };
+ }
+
- if (checks.length > 0) {
const pending = checks.filter((c) => c.status !== "completed");
const failed = checks.filter(
(c) =>
c.status === "completed" &&
c.conclusion !== "success" &&
c.conclusion !== "skipped" &&
c.conclusion !== "neutral",
);
if (pending.length === 0) {
logStep(`All checks complete for PR #${prNumber}`, false, {
total: checks.length,
failed: failed.length,
});
return {
allPassed: failed.length === 0,
failedChecks: failed.map((c) => c.name),
pendingChecks: [],
};
}
logStep(`Waiting for ${pending.length} checks on PR #${prNumber}`, false, {
pending: pending.map((c) => c.name),
});
- }
await wait.for({ seconds: 30 });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (checks.length > 0) { | |
| const pending = checks.filter((c) => c.status !== "completed"); | |
| const failed = checks.filter( | |
| (c) => | |
| c.status === "completed" && | |
| c.conclusion !== "success" && | |
| c.conclusion !== "skipped" && | |
| c.conclusion !== "neutral", | |
| ); | |
| if (pending.length === 0) { | |
| logStep(`All checks complete for PR #${prNumber}`, false, { | |
| total: checks.length, | |
| failed: failed.length, | |
| }); | |
| return { | |
| allPassed: failed.length === 0, | |
| failedChecks: failed.map((c) => c.name), | |
| pendingChecks: [], | |
| }; | |
| } | |
| logStep(`Waiting for ${pending.length} checks on PR #${prNumber}`, false, { | |
| pending: pending.map((c) => c.name), | |
| }); | |
| } | |
| await wait.for({ seconds: 30 }); | |
| } | |
| // If no checks are configured, consider it passed | |
| if (checks.length === 0) { | |
| logStep(`No check runs found for PR #${prNumber}`, false); | |
| return { allPassed: true, failedChecks: [], pendingChecks: [] }; | |
| } | |
| const pending = checks.filter((c) => c.status !== "completed"); | |
| const failed = checks.filter( | |
| (c) => | |
| c.status === "completed" && | |
| c.conclusion !== "success" && | |
| c.conclusion !== "skipped" && | |
| c.conclusion !== "neutral", | |
| ); | |
| if (pending.length === 0) { | |
| logStep(`All checks complete for PR #${prNumber}`, false, { | |
| total: checks.length, | |
| failed: failed.length, | |
| }); | |
| return { | |
| allPassed: failed.length === 0, | |
| failedChecks: failed.map((c) => c.name), | |
| pendingChecks: [], | |
| }; | |
| } | |
| logStep(`Waiting for ${pending.length} checks on PR #${prNumber}`, false, { | |
| pending: pending.map((c) => c.name), | |
| }); | |
| await wait.for({ seconds: 30 }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/github/waitForPRChecks.ts` around lines 60 - 88, When
checksData.check_runs is empty the polling loop never exits; update the logic in
waitForPRChecks (the loop that reads checks and computes pending/failed using
the checks variable) to detect checks.length === 0 and return early (e.g., log
that no checks exist and return { allPassed: true, failedChecks: [],
pendingChecks: [] }) so repositories without CI don't poll until timeout.
| const response = await fetch("https://slack.com/api/chat.postMessage", { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: `Bearer ${token}`, | ||
| }, | ||
| body: JSON.stringify({ channel: channelId, text }), | ||
| }); |
There was a problem hiding this comment.
Consider wrapping fetch in try-catch for network failures.
The function handles Slack API errors (ok: false) but doesn't catch network-level failures (DNS resolution, connection timeouts). If fetch throws, the error propagates uncaught, which may be unexpected given the function signature implies it returns boolean for all outcomes.
🛡️ Proposed fix to handle network errors
export async function postToSlackChannel(channelId: string, text: string): Promise<boolean> {
const token = process.env.SLACK_BOT_TOKEN;
if (!token) {
logger.error("Missing SLACK_BOT_TOKEN — cannot post to Slack");
return false;
}
+ let response: Response;
+ try {
+ response = await fetch("https://slack.com/api/chat.postMessage", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({ channel: channelId, text }),
+ });
+ } catch (err) {
+ logger.error("Network error posting to Slack", { channel: channelId, error: String(err) });
+ return false;
+ }
- const response = await fetch("https://slack.com/api/chat.postMessage", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- body: JSON.stringify({ channel: channelId, text }),
- });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await fetch("https://slack.com/api/chat.postMessage", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| body: JSON.stringify({ channel: channelId, text }), | |
| }); | |
| let response: Response; | |
| try { | |
| response = await fetch("https://slack.com/api/chat.postMessage", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| body: JSON.stringify({ channel: channelId, text }), | |
| }); | |
| } catch (err) { | |
| logger.error("Network error posting to Slack", { channel: channelId, error: String(err) }); | |
| return false; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/slack/postToSlackChannel.ts` around lines 19 - 26, postToSlackChannel
should guard the fetch call against network-level failures by wrapping the await
fetch(...) in a try-catch inside the postToSlackChannel function; on catch, log
the caught error (including error.message) and return false so the function
consistently returns a boolean for network errors, while preserving existing
handling of API responses (response.ok and parsed body). Locate the fetch
invocation and the subsequent response handling (variables named response and
the JSON parsing) and move the fetch+response parsing into the try block and
return false from the catch after logging contextual info (channelId/token
context as appropriate).
| // Step 5a: Wait for all CI checks to complete | ||
| logStep(`Waiting for checks on PR #${pr.number}`); | ||
| const checkResult = await waitForPRChecks(pr.repo, pr.number, PR_CHECK_TIMEOUT_MS); | ||
| logStep(`Checks complete for PR #${pr.number}`, true, { | ||
| allPassed: checkResult.allPassed, | ||
| failedChecks: checkResult.failedChecks, | ||
| }); |
There was a problem hiding this comment.
PRs are merged regardless of CI check failures.
The code logs checkResult.failedChecks but proceeds to merge even when allPassed is false. This could merge broken code into main. Consider gating the merge on CI success.
🐛 Proposed fix to gate merge on CI success
+ // Skip merge if CI checks failed
+ if (!checkResult.allPassed) {
+ logger.warn(`Skipping merge for PR #${pr.number} due to failed checks`, {
+ failedChecks: checkResult.failedChecks,
+ pendingChecks: checkResult.pendingChecks,
+ });
+ continue;
+ }
+
// Step 6: Merge the PR
logStep(`Merging PR #${pr.number} in ${pr.repo}`);
const merged = await mergePR(pr.repo, pr.number);Also applies to: 159-168
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/tasks/agentDayTask.ts` around lines 88 - 94, The PR merge is proceeding
regardless of CI results: use the result returned by waitForPRChecks
(checkResult) to gate merging—if checkResult.allPassed is false, abort the merge
flow, log an error via logStep including checkResult.failedChecks, and return or
throw to stop further actions; only proceed to the existing merge logic when
checkResult.allPassed is true (reference waitForPRChecks, checkResult, logStep,
PR_CHECK_TIMEOUT_MS).
| for (let iteration = 0; iteration < MAX_REVIEW_ITERATIONS; iteration++) { | ||
| logStep(`Review iteration ${iteration + 1} for PR #${pr.number}`); | ||
|
|
||
| const feedback = await fetchPRReviews(pr.repo, pr.number); | ||
| const assessment = await assessPRFeedback(aiSandbox, pr.repo, featurePrompt, feedback); | ||
|
|
||
| logStep(`Feedback assessed for PR #${pr.number}`, true, { | ||
| hasActionableFeedback: assessment.hasActionableFeedback, | ||
| summary: assessment.feedbackSummary, | ||
| }); | ||
|
|
||
| if (!assessment.hasActionableFeedback) { | ||
| break; // Nothing to address — move on | ||
| } | ||
|
|
||
| // Step 5c: Apply the feedback via the update-pr task | ||
| logStep(`Applying feedback for PR #${pr.number}: ${assessment.feedbackSummary}`); | ||
| const updateResult = await updatePRTask.triggerAndWait({ | ||
| feedback: assessment.implementation, | ||
| snapshotId: reviewSnapshotId, | ||
| branch, | ||
| repo: pr.repo, | ||
| }); | ||
|
|
||
| if (!updateResult.ok) { | ||
| logger.warn(`Failed to apply feedback for PR #${pr.number}`, { | ||
| error: updateResult.error, | ||
| }); | ||
| break; | ||
| } | ||
|
|
||
| reviewSnapshotId = updateResult.output.snapshotId; | ||
| currentSnapshotId = reviewSnapshotId; | ||
|
|
||
| // Wait for the new commit to be picked up by CI before re-checking | ||
| await wait.for({ seconds: 30 }); |
There was a problem hiding this comment.
Review loop doesn't re-check CI after applying feedback.
After applying feedback via updatePRTask (line 116), the loop waits 30 seconds but only re-assesses PR feedback without verifying that CI passes on the new commits. A PR with failing checks after the final iteration would still proceed to merge.
Consider adding a waitForPRChecks call after applying feedback to ensure the fix didn't break CI before the next iteration or before merge.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/tasks/agentDayTask.ts` around lines 99 - 134, After a successful
updatePRTask.triggerAndWait, call and await a waitForPRChecks function (e.g.,
waitForPRChecks(pr.repo, pr.number, currentSnapshotId)) before continuing the
review loop; if waitForPRChecks returns a non-passing state (timeout/failure),
log a warning with details (use logger.warn) and break the loop to avoid
proceeding to merge on failing CI, otherwise continue as before, ensuring the
call is placed after updateResult.ok handling and before the next iteration’s
reassessment (replace or augment the existing wait.for({ seconds: 30 }) with
this check so CI status is validated).
src/ai/generateFeaturePrompt.ts
Outdated
| } | ||
| } | ||
|
|
||
| function getFallbackPrompt(): string { |
There was a problem hiding this comment.
srp - move any functions with names different from the file name to a different function.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of automatically merging PRs, agent-day now sends a Telegram message with PR links so a human can review and merge manually. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
agent-dayscheduled task (Trigger.dev) that runs every Sunday at 10 AM ETcallbackThreadIdoptional incoding-agentandupdate-prschemas (backward compatible)How it works
coding-agenttask with the AI-generated promptupdate-pr(max 3 iterations), tests Vercel preview health endpoint#C08HN8RKJHZNew files
src/tasks/agentDayTask.ts— main Sunday cron tasksrc/github/fetchRecentSubmoduleCommits.tssrc/github/waitForPRChecks.tssrc/github/fetchPRReviews.tssrc/github/mergePR.tssrc/github/getVercelPreviewUrl.tssrc/slack/postToSlackChannel.tssrc/ai/generateFeaturePrompt.tssrc/ai/assessPRFeedback.tsTests
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Refactor
Tests