Skip to content

Commit 069bef4

Browse files
authored
fix: address PR review findings and scope reviewer to diff only (#23)
* fix: address PR review findings and scope reviewer to diff only * test: update conclusion-job failure test to expect proceed-on-error behavior * fix: retry conclusion job check once then fail closed; blank workflow name over misattributed companion * fix: restore findExistingComment error handling — fall through to create on transient list failure
1 parent 4e671db commit 069bef4

8 files changed

Lines changed: 611 additions & 594 deletions

File tree

.github/workflows/agent-review.lock.yml

Lines changed: 542 additions & 571 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/agent-review.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ permissions:
1919
pull-requests: read
2020

2121
tools:
22-
bash: ["cat", "head", "tail", "grep", "wc", "ls", "find"]
2322
github:
2423
toolsets: [repos, pull_requests]
2524

@@ -41,19 +40,21 @@ Review PR #{{ github.event.pull_request.number }}: "{{ github.event.pull_request
4140

4241
## Review Process
4342

44-
1. **Fetch the PR diff** using the GitHub tool.
45-
2. **Review for:**
43+
1. **Fetch the PR diff** using the GitHub tool (`get-pull-request-diff` or equivalent).
44+
2. **Review only the changed lines** (`+` lines in the diff). Do not fetch or read full file contents — your entire review must be scoped to lines present in the diff.
45+
3. **Review for:**
4646
- TypeScript type safety issues (strict mode — no `any`, no unchecked nulls)
4747
- Code style: JSDoc block above every function, alphabetical params, explicit return types
4848
- Logic errors or bugs
4949
- Missing test coverage for new logic
50-
3. **Post inline review comments** on specific lines where you find issues.
51-
4. **Submit your review:**
50+
4. **Post inline review comments** only on lines that appear in the diff.
51+
5. **Submit your review:**
5252
- No issues: use `noop` with a brief summary.
5353
- Issues found: COMMENT with specific, actionable feedback.
5454

5555
## Important
5656

57+
- **Only comment on lines in the diff.** Do not flag pre-existing code that was not touched by this PR.
5758
- Be concise and constructive.
5859
- Don't nitpick formatting — Biome handles that.
5960
- Focus on correctness and type safety.

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,12 @@ If you're not using gh-aw, add these two steps to your agent job to write and up
290290
- name: Write token counts
291291
if: always()
292292
run: |
293-
printf '{"input_tokens":%s,"output_tokens":%s,"cache_read_tokens":%s,"cache_write_tokens":%s,"turns":%s}\n' \
294-
"$INPUT_TOKENS" "$OUTPUT_TOKENS" "$CACHE_READ_TOKENS" "$CACHE_WRITE_TOKENS" "$TURNS" \
293+
turns_field=""
294+
if [ -n "$TURNS" ]; then
295+
turns_field=",\"turns\":$TURNS"
296+
fi
297+
printf '{"input_tokens":%s,"output_tokens":%s,"cache_read_tokens":%s,"cache_write_tokens":%s%s}\n' \
298+
"$INPUT_TOKENS" "$OUTPUT_TOKENS" "$CACHE_READ_TOKENS" "$CACHE_WRITE_TOKENS" "$turns_field" \
295299
> /tmp/agent-tokens.json
296300
297301
- uses: actions/upload-artifact@v4

__tests__/workflow-run.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ describe('resolveWorkflowRun', () => {
379379
expect(result.tokens?.cacheWriteTokens).toBe(0);
380380
});
381381

382-
it('skips when listJobsForWorkflowRun fails (fail closed to prevent double-ingest)', async () => {
382+
it('retries once then skips when listJobsForWorkflowRun fails twice (fail closed)', async () => {
383383
const octokit = makeOctokit({});
384384
octokit.rest.actions.listJobsForWorkflowRun = vi
385385
.fn()
@@ -389,11 +389,28 @@ describe('resolveWorkflowRun', () => {
389389
const result = await resolveWorkflowRun(baseArgs);
390390

391391
expect(result.shouldProceed).toBe(false);
392+
expect(octokit.rest.actions.listJobsForWorkflowRun).toHaveBeenCalledTimes(2);
392393
expect(vi.mocked(core.warning)).toHaveBeenCalledWith(
393394
expect.stringContaining('could not check conclusion job status')
394395
);
395396
});
396397

398+
it('proceeds when listJobsForWorkflowRun succeeds on retry after transient failure', async () => {
399+
const octokit = makeOctokit({ conclusionJobStatus: 'completed' });
400+
octokit.rest.actions.listJobsForWorkflowRun = vi
401+
.fn()
402+
.mockRejectedValueOnce(new Error('500 server error'))
403+
.mockResolvedValueOnce({
404+
data: { jobs: [{ name: 'conclusion', status: 'completed', conclusion: 'success' }] },
405+
} as never);
406+
mockGetOctokit.mockReturnValue(octokit as never);
407+
408+
const result = await resolveWorkflowRun(baseArgs);
409+
410+
expect(result.shouldProceed).toBe(true);
411+
expect(octokit.rest.actions.listJobsForWorkflowRun).toHaveBeenCalledTimes(2);
412+
});
413+
397414
it('warns and returns partial data when workflow run API fails', async () => {
398415
const octokit = makeOctokit({});
399416
octokit.rest.actions.getWorkflowRun = vi.fn().mockRejectedValue(new Error('API error'));

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/comment.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,12 @@ async function findExistingComment({
314314
const existing = comments.find((c) => c.body?.includes(COMMENT_MARKER));
315315
if (!existing) return null;
316316
return { id: existing.id, body: existing.body ?? '' };
317-
} catch {
317+
} catch (error) {
318+
// Log and return null so upsertComment falls through to createComment.
319+
// A possible duplicate is preferable to silently dropping the comment entirely.
320+
core.warning(
321+
`AgentMeter: could not list comments to find existing — will create new: ${error}`
322+
);
318323
return null;
319324
}
320325
}

src/run.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ export async function run(): Promise<void> {
9999
if (!inputs.triggerEvent) resolvedTriggerEvent = runData.triggerEvent;
100100
resolvedTriggerRef = runData.triggerRef;
101101
resolvedTriggerType = runData.triggerType;
102-
// Always use the agent workflow's name from the run data — ctx.workflowName is the
103-
// companion workflow and would misattribute if used as a fallback here.
102+
// Always use the agent workflow's name from run data, even if empty. A blank name is
103+
// preferable to silently inheriting ctx.workflowName (the companion workflow), which would
104+
// misattribute ingests in the dashboard.
104105
resolvedWorkflowName = runData.workflowName;
105106
workflowRunTokens = runData.tokens;
106107
artifactTurns = runData.artifactTurns;
@@ -138,11 +139,17 @@ export async function run(): Promise<void> {
138139
// Fall back to resolvedTriggerRef from resolveTrigger (companion workflow_run mode — it knows
139140
// whether a PR was found regardless of the triggering event name, e.g. issue_comment on a PR).
140141
// Last resort: buildTriggerRef from the event name and number.
142+
// Prefer resolvedTriggerType (set by resolveTrigger in workflow_run mode — already knows
143+
// PR vs issue) over resolvedTriggerEvent when building the ref label, so a manual
144+
// trigger_number without trigger_event doesn't default to the generic '#N' form for PRs.
141145
const triggerRef =
142146
ctx.triggerRef ??
143147
resolvedTriggerRef ??
144148
(resolvedTriggerNumber !== null
145-
? buildTriggerRef({ eventName: resolvedTriggerEvent, number: resolvedTriggerNumber })
149+
? buildTriggerRef({
150+
eventName: resolvedTriggerType ?? resolvedTriggerEvent,
151+
number: resolvedTriggerNumber,
152+
})
146153
: null);
147154

148155
// resolvedTriggerType is only set in companion workflow_run mode (from resolveTrigger).

src/workflow-run.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ async function checkConclusionJobCompleted({
180180
/** Workflow run ID */
181181
workflowRunId: number;
182182
}): Promise<boolean> {
183-
try {
183+
const attemptCheck = async (): Promise<boolean> => {
184184
const { data } = await octokit.rest.actions.listJobsForWorkflowRun({
185185
owner,
186186
repo,
@@ -198,12 +198,24 @@ async function checkConclusionJobCompleted({
198198
}
199199
core.info(`AgentMeter: conclusion job completed (${conclusionJob.conclusion}) — proceeding.`);
200200
return true;
201-
} catch (error) {
202-
// Fail closed on API errors — proceeding on a failed gate check risks double-ingest.
203-
// A missing conclusion job (non-gh-aw workflow) is handled above as a successful
204-
// API call that returns no matching job, not as an exception.
205-
core.warning(`AgentMeter: could not check conclusion job status: ${error}. Skipping.`);
206-
return false;
201+
};
202+
203+
try {
204+
return await attemptCheck();
205+
} catch (firstError) {
206+
core.warning(
207+
`AgentMeter: could not check conclusion job status (attempt 1): ${firstError}. Retrying…`
208+
);
209+
try {
210+
return await attemptCheck();
211+
} catch (secondError) {
212+
// Both attempts failed — fail closed to prevent duplicate ingest on persistent API errors
213+
// (e.g. under-scoped token). The retry above already handled transient one-shot failures.
214+
core.warning(
215+
`AgentMeter: could not check conclusion job status (attempt 2): ${secondError}. Skipping.`
216+
);
217+
return false;
218+
}
207219
}
208220
}
209221

@@ -334,9 +346,9 @@ async function resolveTrigger({
334346
}
335347
}
336348

337-
// gh-aw issue branches are named agent/issue-N — require the full prefix to
338-
// avoid mismatching unrelated branches like feature/fix-issue-12-auth.
339-
const issueMatch = headBranch.match(/\bagent\/issue-(\d+)\b/);
349+
// gh-aw issue branches are named agent/issue-N — anchor to the full branch name to
350+
// avoid substring matches in longer branch names like feature/agent/issue-12-fix.
351+
const issueMatch = headBranch.match(/^agent\/issue-(\d+)$/);
340352
if (issueMatch?.[1]) {
341353
const num = parseInt(issueMatch[1], 10);
342354
return {

0 commit comments

Comments
 (0)