Skip to content

scheduled refresh PR #114 @ 48d6c9cf11db1afe425a11adbb213ee527f56ad2 #3919

scheduled refresh PR #114 @ 48d6c9cf11db1afe425a11adbb213ee527f56ad2

scheduled refresh PR #114 @ 48d6c9cf11db1afe425a11adbb213ee527f56ad2 #3919

name: pr-agent-context-refresh
run-name: >-
${{
github.event_name == 'workflow_dispatch' &&
format(
'scheduled refresh PR #{0} @ {1}',
github.event.inputs.pull_request_number,
github.event.inputs.pull_request_head_sha
) ||
github.event_name == 'schedule' &&
'scheduled refresh fanout' ||
github.event_name == 'pull_request_review' &&
format('review refresh PR #{0}', github.event.pull_request.number) ||
github.event_name == 'pull_request_review_comment' &&
format('review-comment refresh PR #{0}', github.event.pull_request.number) ||
github.event_name == 'check_run' &&
format(
'check refresh {0}',
github.event.check_run.pull_requests[0].number || github.event.check_run.head_sha
) ||
github.workflow
}}
on:
pull_request_review:
types: [submitted, edited, dismissed]
pull_request_review_comment:
types: [created, edited, deleted]
check_run:
types: [completed]
workflow_dispatch:
inputs:
pull_request_number:
description: Pull request number to refresh explicitly.
required: true
type: string
pull_request_base_sha:
description: Base SHA for the explicit refresh target.
required: true
type: string
pull_request_head_sha:
description: Head SHA for the explicit refresh target.
required: true
type: string
trigger_event_name:
description: Synthetic trigger event name used in rendered metadata.
required: false
default: workflow_dispatch
type: string
trigger_event_action:
description: Synthetic trigger event action used in rendered metadata.
required: false
default: ""
type: string
schedule:
- cron: "*/15 * * * *"
concurrency:
group: >-
${{
github.event_name == 'workflow_dispatch' &&
format(
'{0}-{1}-{2}',
github.workflow,
github.event.inputs.pull_request_number,
github.event.inputs.pull_request_head_sha
) ||
format(
'{0}-{1}',
github.workflow,
github.event.pull_request.number ||
github.event.check_run.pull_requests[0].number ||
github.event.check_run.head_sha ||
github.ref_name ||
github.sha
)
}}
cancel-in-progress: ${{ github.event_name != 'workflow_dispatch' }}
permissions:
contents: read
actions: read
pull-requests: write
jobs:
dispatch-scheduled-refreshes:
name: Dispatch scheduled refreshes
if: ${{ github.event_name == 'schedule' }}
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
pull-requests: read
steps:
- uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const pulls = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
const sameRepoPulls = pulls.filter(
(pull) => pull.head?.repo?.full_name === `${owner}/${repo}`,
);
const markerPrefix = '<!-- pr-agent-context:managed-comment;';
let dispatchCount = 0;
let skipCount = 0;
let errorCount = 0;
const recentDispatchWindowMs = 60 * 60 * 1000;
const recentDispatchCutoff = Date.now() - recentDispatchWindowMs;
const { data: workflowRunsResponse } = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: 'pr-agent-context-refresh.yml',
event: 'workflow_dispatch',
per_page: 100,
});
const workflowDispatchRuns = workflowRunsResponse.workflow_runs.filter((run) => {
const createdTimestamp = run.created_at ? Date.parse(run.created_at) : 0;
return createdTimestamp >= recentDispatchCutoff || run.status !== 'completed';
});
const hasCurrentRefreshComment = async (pull) => {
const perPage = 100;
const maxCommentsToInspect = 500;
let page = 1;
let inspectedComments = 0;
while (inspectedComments < maxCommentsToInspect) {
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: pull.number,
per_page: perPage,
page,
sort: 'created',
direction: 'desc',
});
if (
comments.some(
(comment) =>
comment.body?.startsWith(markerPrefix) &&
comment.body.includes('execution_mode=refresh;') &&
comment.body.includes(`head_sha=${pull.head.sha};`),
)
) {
return true;
}
inspectedComments += comments.length;
if (comments.length < perPage) {
break;
}
page += 1;
}
return false;
};
const hasRecentOrInFlightScheduledDispatchRun = (pull) => {
const expectedTitle = `scheduled refresh PR #${pull.number} @ ${pull.head.sha}`;
const now = Date.now();
return workflowDispatchRuns.some((run) => {
if (run.display_title !== expectedTitle) {
return false;
}
if (run.status !== 'completed') {
return true;
}
const completedTimestamp = run.updated_at
? new Date(run.updated_at).getTime()
: run.created_at
? new Date(run.created_at).getTime()
: 0;
return completedTimestamp > 0 && now - completedTimestamp <= recentDispatchWindowMs;
});
};
for (const pull of sameRepoPulls) {
try {
const refreshCommentExists = await hasCurrentRefreshComment(pull);
const hasRecentDispatchRun = hasRecentOrInFlightScheduledDispatchRun(pull);
if (refreshCommentExists) {
core.notice(
`Skipping PR #${pull.number}: refresh comment for head ${pull.head.sha} already exists.`,
);
skipCount += 1;
continue;
}
if (hasRecentDispatchRun) {
core.notice(
`Skipping PR #${pull.number}: recent or in-flight scheduled dispatch for head ${pull.head.sha} already exists.`,
);
skipCount += 1;
continue;
}
await github.rest.actions.createWorkflowDispatch({
owner,
repo,
workflow_id: 'pr-agent-context-refresh.yml',
ref: context.ref.replace('refs/heads/', ''),
inputs: {
pull_request_number: String(pull.number),
pull_request_base_sha: pull.base.sha,
pull_request_head_sha: pull.head.sha,
trigger_event_name: 'schedule',
trigger_event_action: '',
},
});
dispatchCount += 1;
} catch (error) {
errorCount += 1;
core.warning(
`Unable to evaluate or dispatch PR #${pull.number}: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
core.notice(
`Scheduled refresh guard dispatched ${dispatchCount} run(s) and skipped ${skipCount}.`,
);
if (errorCount > 0) {
core.warning(`Scheduled refresh guard encountered ${errorCount} per-PR error(s).`);
}
pr-agent-context-refresh:
name: PR agent context refresh
if: >-
github.event_name != 'schedule' &&
(
(github.event_name == 'pull_request_review' &&
github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'pull_request_review_comment' &&
github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'check_run' &&
github.event.action == 'completed' &&
github.event.check_run.app.slug != 'github-actions' &&
toJson(github.event.check_run.pull_requests) != '[]' &&
github.event.check_run.pull_requests[0].head.repo.full_name == github.repository) ||
(github.event_name == 'workflow_dispatch' &&
github.event.inputs.pull_request_number != '' &&
github.event.inputs.pull_request_base_sha != '' &&
github.event.inputs.pull_request_head_sha != '')
)
uses: shaypal5/pr-agent-context/.github/workflows/pr-agent-context.yml@v4
with:
tool_ref: v4
execution_mode: refresh
trigger_event_name_override: ${{ github.event.inputs.trigger_event_name || '' }}
trigger_event_action_override: ${{ github.event.inputs.trigger_event_action || '' }}
pull_request_number_override: ${{ github.event.inputs.pull_request_number || '' }}
pull_request_base_sha_override: ${{ github.event.inputs.pull_request_base_sha || '' }}
pull_request_head_sha_override: ${{ github.event.inputs.pull_request_head_sha || '' }}
publish_mode: append
publish_all_clear_comments_in_refresh: false
wait_for_reviews_to_settle: true
target_patch_coverage: "100"
include_review_comments: true
include_outdated_review_threads: true
include_failing_checks: true
include_failed_step_output: true
include_patch_coverage: true
coverage_artifact_prefix: pr-agent-context-coverage
enable_cross_run_coverage_lookup: true
coverage_source_workflows: CI
prompt_template_file: .github/pr-agent-context-template.md
debug_artifacts: true