fix(action): fetch PR head sha when event payload has none - #404
Merged
Merged
Conversation
Runs triggered by schedule or workflow_dispatch have no pull_request payload, so the same-commit skip never engaged and every run deleted and re-posted the review (uBlock sync fork runs hourly with debounce disabled). Fall back to fetching the head sha from the pull request API.
Contributor
|
bedrock debug - [puLL-Merge] - brave/pull-merge@404 Diffdiff --git action.cjs action.cjs
index 16d9f01..dfb2537 100644
--- action.cjs
+++ action.cjs
@@ -92,9 +92,27 @@ module.exports = async ({ github, context, inputs, actionPath }) => {
debug
})
- const headSha = context.payload.pull_request && context.payload.pull_request.head
+ // head commit sha: prefer the pull_request event payload, otherwise
+ // (schedule / workflow_dispatch runs) fetch it from the PR so the
+ // same-commit skip can still fire
+ const fetchHeadSha = async () => {
+ try {
+ if (!Number.isFinite(options.prnum)) return undefined
+ const prResponse = await github.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', {
+ owner: options.owner,
+ repo: options.repo,
+ pull_number: options.prnum
+ })
+ return prResponse.data.head.sha
+ } catch (error) {
+ if (debug) console.log(`failed to fetch PR head sha: ${error.message}`)
+ return undefined
+ }
+ }
+
+ const headSha = context.payload.pull_request && context.payload.pull_request.head && context.payload.pull_request.head.sha
? context.payload.pull_request.head.sha
- : undefined
+ : await fetchHeadSha()
const explainPatchCb = async () => await explainPatch({
apiKey: options.key,
diff --git test/features/action.feature test/features/action.feature
index 5cc4289..bc4f37e 100644
--- test/features/action.feature
+++ test/features/action.feature
@@ -293,3 +293,26 @@ Feature: action orchestrator
Then it resolves to undefined
And a comment was created containing "<!-- Generated by gpt-5.3-codex @ deadbeef -->"
And the puLL-Merge label was added
+
+ Scenario: a schedule run skips re-commenting when the API head commit was already reviewed
+ Given the action context has no pull request payload
+ And the PR head sha from the API is "deadbeef"
+ And the PR has these comments:
+ | id | author | body | age_hours |
+ | C1 | github-actions[bot] | [[puLL-Merge](https://github.com/brave/pull-merge)] - [brave/pull-merge@42](https://github.com/brave/pull-merge/pull/42)<!-- Generated by gpt-5.3-codex @ deadbeef --> | 48 |
+ When the action runs
+ Then it resolves to undefined
+ And no comment was created
+ And no labels were added
+ And 0 chat completions were called
+
+ Scenario: a schedule run re-comments when the API head commit changed
+ Given the action context has no pull request payload
+ And the PR head sha from the API is "deadbeef"
+ And the PR has these comments:
+ | id | author | body | age_hours |
+ | C1 | github-actions[bot] | [[puLL-Merge](https://github.com/brave/pull-merge)] - [brave/pull-merge@42](https://github.com/brave/pull-merge/pull/42)<!-- Generated by gpt-5.3-codex @ cafe1234 --> | 48 |
+ When the action runs
+ Then it resolves to undefined
+ And a comment was created containing "<!-- Generated by gpt-5.3-codex @ deadbeef -->"
+ And the puLL-Merge label was added
diff --git test/steps/action.mjs test/steps/action.mjs
index 976ea07..7dd5a6b 100644
--- test/steps/action.mjs
+++ test/steps/action.mjs
@@ -28,6 +28,13 @@ Given('the action context has PR head sha {string}', function (sha) {
this.prHeadSha = sha
})
+Given('the PR head sha from the API is {string}', function (sha) {
+ mockState().github.requestRoutes.push({
+ match: (route, opts) => route === PULLS_ROUTE && opts?.mediaType?.format !== 'diff',
+ reply: { data: { head: { sha } } }
+ })
+})
+
Given('the action inputs:', function (doc) {
this.actionInputs = JSON.parse(trimDoc(doc))
})
DescriptionEnables same-commit skip logic for schedule/workflow_dispatch runs by fetching the PR head SHA from the GitHub API when the Possible Issues
ChangesChanges
sequenceDiagram
participant Trigger as schedule/workflow_dispatch
participant Action as action.cjs
participant GH_API as GitHub API
Trigger->>Action: run (no pull_request payload)
Action->>Action: context.payload.pull_request falsy
Action->>GH_API: GET /repos/{owner}/{repo}/pulls/{prnum}
GH_API-->>Action: { head: { sha: "deadbeef" } }
Action->>Action: headSha = "deadbeef"
Action->>Action: check existing comments for sha match
alt sha already reviewed
Action-->>Trigger: resolve undefined (skip)
else sha changed
Action->>GH_API: create comment with new sha
Action->>GH_API: add puLL-Merge label
Action-->>Trigger: resolve undefined
end
|
Contributor
|
anthropic debug - [puLL-Merge] - brave/pull-merge@404 Diffdiff --git action.cjs action.cjs
index 16d9f01..dfb2537 100644
--- action.cjs
+++ action.cjs
@@ -92,9 +92,27 @@ module.exports = async ({ github, context, inputs, actionPath }) => {
debug
})
- const headSha = context.payload.pull_request && context.payload.pull_request.head
+ // head commit sha: prefer the pull_request event payload, otherwise
+ // (schedule / workflow_dispatch runs) fetch it from the PR so the
+ // same-commit skip can still fire
+ const fetchHeadSha = async () => {
+ try {
+ if (!Number.isFinite(options.prnum)) return undefined
+ const prResponse = await github.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', {
+ owner: options.owner,
+ repo: options.repo,
+ pull_number: options.prnum
+ })
+ return prResponse.data.head.sha
+ } catch (error) {
+ if (debug) console.log(`failed to fetch PR head sha: ${error.message}`)
+ return undefined
+ }
+ }
+
+ const headSha = context.payload.pull_request && context.payload.pull_request.head && context.payload.pull_request.head.sha
? context.payload.pull_request.head.sha
- : undefined
+ : await fetchHeadSha()
const explainPatchCb = async () => await explainPatch({
apiKey: options.key,
diff --git test/features/action.feature test/features/action.feature
index 5cc4289..bc4f37e 100644
--- test/features/action.feature
+++ test/features/action.feature
@@ -293,3 +293,26 @@ Feature: action orchestrator
Then it resolves to undefined
And a comment was created containing "<!-- Generated by gpt-5.3-codex @ deadbeef -->"
And the puLL-Merge label was added
+
+ Scenario: a schedule run skips re-commenting when the API head commit was already reviewed
+ Given the action context has no pull request payload
+ And the PR head sha from the API is "deadbeef"
+ And the PR has these comments:
+ | id | author | body | age_hours |
+ | C1 | github-actions[bot] | [[puLL-Merge](https://github.com/brave/pull-merge)] - [brave/pull-merge@42](https://github.com/brave/pull-merge/pull/42)<!-- Generated by gpt-5.3-codex @ deadbeef --> | 48 |
+ When the action runs
+ Then it resolves to undefined
+ And no comment was created
+ And no labels were added
+ And 0 chat completions were called
+
+ Scenario: a schedule run re-comments when the API head commit changed
+ Given the action context has no pull request payload
+ And the PR head sha from the API is "deadbeef"
+ And the PR has these comments:
+ | id | author | body | age_hours |
+ | C1 | github-actions[bot] | [[puLL-Merge](https://github.com/brave/pull-merge)] - [brave/pull-merge@42](https://github.com/brave/pull-merge/pull/42)<!-- Generated by gpt-5.3-codex @ cafe1234 --> | 48 |
+ When the action runs
+ Then it resolves to undefined
+ And a comment was created containing "<!-- Generated by gpt-5.3-codex @ deadbeef -->"
+ And the puLL-Merge label was added
diff --git test/steps/action.mjs test/steps/action.mjs
index 976ea07..7dd5a6b 100644
--- test/steps/action.mjs
+++ test/steps/action.mjs
@@ -28,6 +28,13 @@ Given('the action context has PR head sha {string}', function (sha) {
this.prHeadSha = sha
})
+Given('the PR head sha from the API is {string}', function (sha) {
+ mockState().github.requestRoutes.push({
+ match: (route, opts) => route === PULLS_ROUTE && opts?.mediaType?.format !== 'diff',
+ reply: { data: { head: { sha } } }
+ })
+})
+
Given('the action inputs:', function (doc) {
this.actionInputs = JSON.parse(trimDoc(doc))
})
DescriptionAdds fallback head-SHA resolution in Possible Issues
ChangesChangesaction.cjs
test/features/action.feature
test/steps/action.mjs
sequenceDiagram
participant WF as Workflow (schedule)
participant A as action.cjs
participant GH as GitHub API
participant LLM as LLM
WF->>A: run(github, context, inputs)
A->>GH: list comments
GH-->>A: existing bot comment (sha marker)
alt payload.pull_request.head.sha present
A->>A: headSha = payload sha
else missing (schedule/dispatch)
A->>GH: GET /repos/{owner}/{repo}/pulls/{pull_number}
GH-->>A: data.head.sha (or error -> undefined)
end
alt marker sha === headSha
A-->>WF: undefined (skip, no LLM call)
else differs or undefined
A->>LLM: explainPatch(diff)
LLM-->>A: explanation
A->>GH: create comment w/ marker @ headSha
A->>GH: add puLL-Merge label
A-->>WF: undefined
end
|
antonok-edm
approved these changes
Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Runs triggered by
schedule/workflow_dispatchhave nopull_requestpayload, socontext.payload.pull_request.head.shais undefined and the same-commit skip from #402 never engaged. Example: brave/uBlocksync-from-forkruns puLL-Merge hourly on PR #336 (mirror branch, unchanged head) withdebounce_time: 0— every run deleted and re-posted the review (run log:Deleting 1 message(s)).Fix
context.payload.pull_request.head.shawhen present (unchanged behavior).GET /repos/{owner}/{repo}/pulls/{pull_number}using the already-known owner/repo/prnum.Test
BDD: 2 new scenarios in
action.feature— schedule run skips when API head sha matches the reviewed marker; schedule run re-comments when it changed. 178/178 green locally and in the Ubuntu VM.