pr-agent-context is a reusable GitHub Actions tool that assembles managed
PR handoff comments for coding agents.
Created by Shay Palachy Affek.
pr-agent-context turns live PR state into a durable handoff comment:
PR review threads + failing checks + patch coverage + caller template
|
v
managed GitHub PR comment for coding agents
|
v
refresh runs keep later review/check signals visible
See an abbreviated generated artifact in
docs/managed-comment-preview.md.
- Downstream Usage: minimal reusable-workflow setup and versioning.
- Prompt Templating: customize the rendered handoff prompt.
- Refresh Mode: keep review/check updates visible after initial CI.
- Coverage Artifact Contract: feed patch coverage from artifacts.
- Debug Artifacts: inspect deterministic JSON and Markdown outputs.
- PR-Wide Failing Checks: understand check aggregation behavior.
- Patch Coverage Rules: know what counts in patch coverage.
Current behavior includes:
- unresolved PR review threads
- PR-wide failing-check aggregation for the PR head SHA
- failed GitHub Actions workflow runs/jobs with trimmed log excerpts
- failed external check runs and commit statuses when GitHub exposes useful metadata
- patch coverage analysis from raw
coverage.pyartifacts or a combinedcoverage.xmlartifact - uncovered changed executable Python lines for the PR diff
- configurable prompt templating from the caller repository
- structured debug artifacts for inspection and downstream automation
- deterministic prompt rendering with stable item IDs
- refresh-capable execution for CI producer runs and later PR lifecycle events
- append-first managed PR comments with optional update modes
- automatic hiding of superseded managed comments after append-mode publishes
- cross-run coverage artifact reuse for refresh invocations on the same PR head SHA
By default, each invocation writes a new managed comment. The tool can also be configured to update the latest managed comment or update the newest comment matching the current run identity.
The managed comment body shape is:
<!-- pr-agent-context:managed-comment; schema=v5; publish_mode=<MODE>; execution_mode=<ci|refresh>; pr=<PR>; head_sha=<HEAD_SHA>; trigger_event=<EVENT>; generated_at=<TIMESTAMP>; tool_ref=<TOOL_REF>; run_id=<RUN_ID>; run_attempt=<ATTEMPT> -->
pr-agent-context report:
```markdown
<rendered prompt>
```
Run metadata:
```
Tool ref: <TOOL_REF>
Tool version: <TOOL_VERSION>
Workflow run: <RUN_ID> attempt <ATTEMPT>
Comment timestamp: <TIMESTAMP>
PR head commit: <HEAD_SHA>
```Short reusable-workflow usage:
jobs:
pr-agent-context:
name: PR agent context
if: ${{ always() && github.event_name == 'pull_request' }}
needs: [test, lint]
permissions:
contents: read
actions: read
pull-requests: write
uses: shaypal5/pr-agent-context/.github/workflows/pr-agent-context.yml@v4
with:
target_patch_coverage: "100"
include_patch_coverage: true
coverage_artifact_prefix: pr-agent-context-coverage
prompt_template_file: .github/pr-agent-context-template.md
debug_artifacts: trueVersioning guidance:
- use
@v4in downstream repositories for the stable major line - publish fixed release tags such as
v4.0.0for exact version pinning - this repository includes
.github/workflows/release-tags.yml, which automatically movesv4when av4.x.ytag is pushed
Example release flow in this repository:
git checkout main
git pull --ff-only origin main
git tag v4.0.1
git push origin v4.0.1After the version tag push completes, the workflow force-updates the matching major tag to the same commit.
The reusable workflow inputs are:
tool_ref: ref ofshaypal5/pr-agent-contextto run, default"v4"execution_mode: controls whether the run behaves like the initial CI pass or a later refresh pass.cialways uses CI behavior,refreshalways uses refresh behavior, andautoinfers the mode from the triggering event, defaultautotrigger_event_name_override: optional explicit trigger event name for dispatch- or schedule-driven refreshes, default""trigger_event_action_override: optional explicit trigger event action paired withtrigger_event_name_override, default""pull_request_number_override: optional explicit pull request number for dispatch- or schedule-driven refreshes, default""pull_request_base_sha_override: optional explicit pull request base SHA for dispatch- or schedule-driven refreshes, default""pull_request_head_sha_override: optional explicit pull request head SHA for dispatch- or schedule-driven refreshes, default""publish_mode: controls how managed PR comments are created or updated, defaultappendappend: when the run publishes a managed comment, post it as a new PR comment instead of updating an existing one; by default, olderpr-agent-contextmanaged comments on the same PR are then hidden asOUTDATED; refresh-mode no-op runs may still skip publishing entirely; this is the recommended refresh-mode setting in this repository because each refresh signal leaves behind its own comment history while keeping the newest result visibleupdate_latest_managed: update the newest managed comment on the PR, regardless of which run lifecycle created itupdate_matching: update the managed comment whose marker matches the current run identityupdate_latest_scoped: update the newest managed comment from the same lifecycle scope (civsrefresh); this keeps refresh updates off the CI comment, but rewrites a single refresh comment in place instead of preserving each refresh snapshot
publish_all_clear_comments_in_refresh: in refresh mode, still publish no-op all-clear comments, defaultfalsehide_previous_managed_comments_on_append: in append mode, hide olderpr-agent-contextmanaged comments on the same PR after posting the new one, defaulttrueinclude_refresh_metadata: include a compact refreshed-snapshot note in the prompt when applicable, defaulttrueinclude_review_comments: include unresolved PR review threads, defaulttrueinclude_outdated_review_threads: when review comments are enabled, keep unresolved threads even if GitHub marks them outdated, defaulttrueinclude_failing_checks: include failing checks in the rendered prompt, defaulttrueinclude_cross_run_failures: expand Actions failure collection from the current run to PR-head-SHA-wide failed runs/jobs, defaulttrueinclude_external_checks: include failed external check runs and commit statuses for the PR head SHA, defaulttrueinclude_failed_step_output: GitHub Actions job failures prefer the failed step's full log block over the compact excerpt, defaulttruemax_failed_step_output_lines: cap the collected failed-step log block before prompt rendering, default500include_approval_gated_actions_run_notes: include a separate informational section for GitHub Actions runs that were waiting for maintainer approval and did not execute jobs, defaultfalsewait_for_checks_to_settle: briefly poll the PR head SHA check universe so late-arriving checks can appear before collection, defaulttruewait_for_reviews_to_settle: in refresh mode, briefly poll unresolved review threads before rendering, defaultfalsetarget_patch_coverage: required patch coverage percentage; explicit input wins, otherwisepr-agent-contextauto-detects the Codecov patch target from.codecov.yml/codecov.ymlwhen present, and falls back to"100"include_patch_coverage: enable patch coverage analysis, defaulttruepatch_coverage_source_mode: controls where patch coverage is reconstructed from, defaultraw_coverage_artifactsraw_coverage_artifacts: use raw.coverage*artifacts and reconstruct patch coverage withcoverage.py; this is the best fit for matrix or otherwise complex producer workflowscoverage_xml_artifact: use a caller-provided combinedcoverage.xmlartifact directly; this is the best fit for simple repos that already publish a final combined report
coverage_artifact_prefix: artifact name prefix for raw.coverage*uploads, defaultpr-agent-context-coveragecoverage_report_artifact_name: exact artifact name for a combined coverage XML report, default""coverage_report_filename: path inside the combined coverage XML artifact, defaultcoverage.xmlenable_cross_run_coverage_lookup: allow refresh-mode runs to reuse coverage artifacts from prior producer runs on the same head SHA, defaulttruecoverage_source_workflows: optional workflow-name filter for reusable coverage producer runscoverage_source_conclusions: allowed producer run conclusions, defaultsuccesscoverage_selection_strategy: controls how a prior producer run is chosen when more than one matching run is available; currentlylatest_successfulis supported and selects the newest successful matching producer, defaultlatest_successfulfork_behavior: fork degradation policy, defaultbest_effortforce_patch_coverage_section: render the# Patch coveragesection even when patch coverage is not actionable, defaultfalseprompt_preamble: optional text inserted near the top of the rendered promptprompt_template_file: optional template file path in the caller repository workspacecopilot_author_patterns: comma- or newline-separated exact logins andre:regexes for Copilot actor matchingdebug_artifacts: upload JSON/markdown debug artifacts, defaulttruedebug_artifact_prefix: artifact name prefix for uploaded debug bundles, defaultpr-agent-context-debugmax_review_threads: cap unresolved review threads, default50max_actions_runs: cap completed workflow runs inspected for the PR head SHA, default20max_actions_jobs: cap failed Actions jobs included after aggregation and dedupe, default20max_external_checks: cap failed external check/status items included after normalization, default20max_failing_checks: cap the total rendered failing-check items after dedupe, default25max_log_lines_per_job: cap collected failed-job excerpt lines before rendering, default80check_settle_timeout_seconds: maximum seconds to wait for the head-SHA check universe to settle, default45check_settle_poll_interval_seconds: polling interval in seconds while waiting for late checks, default5review_settle_timeout_seconds: maximum seconds to wait for unresolved review threads to stabilize in refresh mode, default180review_settle_poll_interval_seconds: polling interval while waiting for review-thread stability, default10characters_per_line: wrap plain prose lines in rendered output to this width, default100
By default, unresolved review threads are still included after GitHub marks them outdated. That
keeps unresolved Copilot and human review feedback visible until someone explicitly resolves the
thread. If your repo prefers to ignore stale diff locations, set
include_outdated_review_threads: false.
When failing-check collection is enabled, pr-agent-context now waits briefly for the PR head
SHA check universe to settle before collecting failures. This helps late-arriving external checks
such as Codecov appear in the rendered prompt instead of being missed because they posted a few
seconds after the workflow started.
If prompt_template_file is not provided, pr-agent-context uses the built-in default
template. If it is provided, the file is loaded from the caller repository workspace and
rendered deterministically with a small placeholder renderer.
When repository context is available, the built-in opening instructions include the caller
repository URL in the first sentence, for example in repository https://github.com/owner/repo.
Supported placeholders:
{{ pr_number }}{{ prompt_preamble }}{{ opening_instructions }}{{ copilot_comments_section }}{{ review_comments_section }}{{ failing_checks_section }}{{ approval_gated_actions_run_notes_section }}{{ patch_coverage_section }}
Unknown placeholders and malformed template braces fail fast with a clear validation error.
Example custom template file:
{{ prompt_preamble }}
# PR {{ pr_number }}
{{ opening_instructions }}
{{ copilot_comments_section }}
{{ review_comments_section }}
{{ failing_checks_section }}
{{ approval_gated_actions_run_notes_section }}
{{ patch_coverage_section }}prompt_preamble, when non-empty, is always inserted near the top in a deterministic way,
even if a custom template omits the explicit placeholder.
If a custom template omits {{ copilot_comments_section }} but still includes
{{ review_comments_section }}, pr-agent-context folds Copilot comments into the review
comments placeholder to preserve compatibility with templates created before the split between
Copilot and non-Copilot review sections.
When include_approval_gated_actions_run_notes is enabled, custom templates should include
{{ approval_gated_actions_run_notes_section }} if they want to control where the informational
approval-gated section appears. If the placeholder is omitted, pr-agent-context appends that
section after the rendered template so the information is still preserved.
Rendered output also applies a configurable prose-wrapping pass. By default, plain prose lines are wrapped to 100 characters, while semantically sensitive lines are left untouched:
- fenced code blocks
- indented/code excerpt lines
- headings
- list items
- metadata lines such as
Location:andURL: - lines containing URLs
pr-agent-context now supports both CI producer runs and later refresh-style runs with the same
reusable workflow.
Built-in settling only helps while a currently running invocation is still alive. If later PR signals arrive after that run finishes, the caller repository still needs a tiny refresh workflow to subscribe to those follow-up events and invoke the reusable workflow again in refresh mode.
Recommended minimal caller-side pattern:
- CI producer workflow
- trigger:
pull_request - runs tests and uploads raw
.coverage*artifacts - invokes
pr-agent-contextin normal/auto mode
- Refresh workflow
- triggers:
pull_request_review,pull_request_review_comment - optional additional triggers:
workflow_run,status,check_run,check_suite - optional hardening: add a repo-owned
scheduleplusworkflow_dispatchpath that fans out explicit PR refreshes when bot-authored review events would otherwise leave runs stuck waiting for approval - invokes the same reusable workflow with:
execution_mode: refreshpublish_mode: appendpublish_all_clear_comments_in_refresh: falseenable_cross_run_coverage_lookup: true- optional
wait_for_reviews_to_settle: true
With this lifecycle split, the initial CI run can publish an all-clear comment, while later refresh runs publish a fresh refresh-scoped managed comment whenever they have something actionable to report. The default append-mode hiding behavior can minimize older managed comments afterward so the newest refresh result stays visible without rewriting earlier comments in place.
For production use, prefer a refresh workflow with:
- same-repository guards so fork-originated review/check events do not try to write comments
- concurrency per PR so rapid review/check bursts coalesce
coverage_source_workflowspinned to the CI producer workflow name when refresh runs reuse earlier coverage artifacts- a repo-owned scheduled dispatcher if Copilot or other bot-authored review events can leave direct refresh runs waiting for maintainer approval
Status-driven refreshes can arrive after a PR is closed or merged. In those cases,
pr-agent-context resolves the PR from the trigger SHA and preserves the trigger head SHA for
status, check_run, and check_suite events when GitHub points the lookup back at a closed PR.
Production-hardened refresh workflow example:
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 { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: pull.number,
per_page: 50,
sort: 'created',
direction: 'desc',
});
return comments.some(
(comment) =>
comment.body?.startsWith(markerPrefix) &&
comment.body.includes('execution_mode=refresh;') &&
comment.body.includes(`head_sha=${pull.head.sha};`),
);
};
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:
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
target_patch_coverage: "100"
include_review_comments: true
# Optional: exclude unresolved threads once GitHub marks them outdated.
# include_outdated_review_threads: false
include_failing_checks: true
include_patch_coverage: true
# Optional: disable the default append-mode hiding behavior if you prefer
# older managed comments to remain visible.
# hide_previous_managed_comments_on_append: false
enable_cross_run_coverage_lookup: true
wait_for_reviews_to_settle: true
coverage_artifact_prefix: pr-agent-context-coverage
coverage_source_workflows: CI
debug_artifacts: true
prompt_template_file: .github/pr-agent-context-template.mdAdditional copy-pasteable examples live in examples/:
examples/ci-producer.yml: minimal PR CI + initialpr-agent-contextinvocationexamples/pr-agent-context-refresh.yml: minimal refresh workflow for later review/check signalsexamples/coverage-matrix-producer.yml: matrix coverage producer with raw.coverage*uploadsexamples/pr-agent-context-template.md: simple custom prompt template
Refresh mode is best-effort on forks. When write access, external checks, or artifact access are restricted, the tool records those degradations in debug artifacts instead of failing wholesale.
This repository's own
pr-agent-context-refresh.yml intentionally
calls the local reusable workflow path and uses the PR head SHA as tool_ref when available. That
keeps self-dogfooding branches branch-coupled so new caller inputs cannot drift ahead of the local
reusable workflow contract during review-triggered refreshes.
If Copilot or another bot can leave review-triggered refresh runs waiting for maintainer approval,
the scheduled dispatcher pattern above avoids depending on that blocked run. The schedule wakes up
as the repository itself, enumerates open same-repo PRs, and uses a composite dedupe guard before
re-dispatching: it first checks for a same-head refresh managed comment, then checks for a recent
or in-flight scheduled workflow_dispatch run with the same PR number and head SHA. That second
guard matters when refresh runs suppress all-clear comments, because a no-op refresh may otherwise
leave no managed comment behind. Only when neither signal exists does it dispatch explicit refresh
runs with pull_request_*_override inputs so pr-agent-context can still resolve the PR cleanly.
Patch coverage is computed locally from:
- the caller repo git diff between the PR base SHA and head SHA
- either:
- raw
.coverage*data discovered recursively inside downloaded artifact directories from the current run or from a selected prior producer workflow run for the same PR head SHA, or - a caller-provided combined
coverage.xmlartifact downloaded from the current run or from a selected prior producer workflow run for the same PR head SHA
- raw
The reusable workflow does not scrape the Codecov UI and does not call Codecov APIs.
For refresh invocations, pr-agent-context can reuse artifacts from prior runs:
- it searches workflow runs for the same head SHA
- filters by configured workflow names/conclusions when provided
- deterministically chooses the latest suitable producer run
- downloads matching artifacts and reuses either the raw
coverage.pyflow or the configured combined XML report flow
Coverage-producing jobs in downstream repos must upload raw coverage.py data files in
artifacts whose names start with the configured prefix. Example:
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.11", "3.12"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: python -m pip install -e ".[dev]"
- run: pytest
- name: Upload raw coverage data
if: always()
uses: actions/upload-artifact@v4
with:
name: pr-agent-context-coverage-${{ matrix.os }}-py${{ matrix.python-version }}
path: .coverage*
include-hidden-files: true
if-no-files-found: ignoreEach matrix artifact may keep its own .coverage or .coverage.* filenames; they do not need to
be globally unique across artifacts. The reusable workflow preserves per-artifact directories when
downloading and discovers raw coverage files recursively, so same-named files from sibling matrix
artifacts remain distinct.
For simpler repos that already produce a single combined coverage.xml, you can point
pr-agent-context at that artifact directly and avoid waiting on Codecov patch status
processing:
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python -m coverage combine
- run: python -m coverage xml
- uses: actions/upload-artifact@v4
with:
name: coverage-xml
path: coverage.xml
pr-agent-context:
needs: [coverage]
uses: shaypal5/pr-agent-context/.github/workflows/pr-agent-context.yml@v4
with:
include_patch_coverage: true
patch_coverage_source_mode: coverage_xml_artifact
coverage_report_artifact_name: coverage-xml
coverage_report_filename: coverage.xmlWhen patch_coverage_source_mode=coverage_xml_artifact, pr-agent-context automatically ignores
Codecov check runs and codecov/* commit statuses during check settlement and failing-check
rendering, because the configured coverage artifact is treated as the authoritative patch coverage
source.
When debug_artifacts is enabled, the reusable workflow uploads a debug bundle containing:
collected-context.json: normalized collected review/failure/coverage contextfailing-check-universe.json: raw failing-check universe, deduped failing-check set, source counts, and collector warningscoverage-source.json: coverage-source candidate runs, selected producer run/artifacts, and reuse decisionspull-request-context.json: trigger-derived PR resolution detailsprompt.md: rendered prompt markdown before the managed-comment wrappercomment-body.md: final managed PR comment bodycomment-sync.json: parsed managed comments, current run identity, selected sync target, and publication actionsummary.json: a compact machine-readable summary with counts, booleans, prompt SHA, template diagnostics, and truncation notes
These artifacts are intended for maintainers and downstream automation. They are deterministic JSON/markdown outputs and are not redacted beyond standard secret handling in the workflow environment.
Failing-check collection is PR-wide by default rather than limited to the reusable workflow's own run.
For the PR head SHA, pr-agent-context will attempt to collect:
- failed GitHub Actions workflow runs and failed jobs across runs/reruns
- failed external check runs exposed through GitHub's checks APIs
- failed commit statuses exposed through GitHub's status APIs
The tool deduplicates repeated failures across reruns/check surfaces so the prompt stays compact:
- later successful reruns suppress older failures for the same Actions job identity
- later successful workflow runs suppress older fallback workflow-run failures for the same workflow identity
- repeated failures with the same identity are deduped to the most informative instance
- Actions jobs are preferred over fallback workflow-run items when job details are available
- external check runs and commit statuses are kept distinct unless they are obvious duplicates by context/name
Approval-gated GitHub Actions runs are treated separately from actionable failing checks:
action_requiredActions workflow runs with no jobs are hidden from# Failing Checksby default- when
include_approval_gated_actions_run_notesis enabled, they render in a separate informational section - those notes do not count as failing checks and do not change comment publication behavior on their own
Current-run failures remain first-class: when the current reusable-workflow run contributed the most useful failure instance, it is preferred and rendered first among equivalent failures.
GitHub Actions job failures include richer failed-step output by default:
- by default,
pr-agent-contextattempts to extract the full failed step log block - set
include_failed_step_output: falseto fall back to the compact anchored excerpt-only behavior - failed-step output is capped by
max_failed_step_output_linesbefore render-time section truncation - if the failed step cannot be matched reliably in the log, the tool falls back to the existing excerpt behavior
External checks depend on what GitHub exposes for the PR head SHA. Some providers offer only a
name, summary, and URL; when logs/details are unavailable, pr-agent-context renders a compact
metadata-only item instead of failing the run.
- Only changed Python files are considered.
- Added or modified line numbers come from
git diff --unified=0 <base>...<head>in the checked-out caller repo. - The denominator is changed executable Python lines only.
- Changed non-executable lines do not count against coverage.
- If a changed Python file is in coverage scope but has no measured data, its changed executable lines are treated as uncovered.
- If there are no changed executable Python lines, patch coverage is treated as
N/A. - The
# Patch coveragesection is omitted forN/Apatches unlessforce_patch_coverage_sectionis enabled. - Uncovered lines are rendered as explicit line lists rather than compressed ranges so the output is easy to hand to a coding agent.
By default, pr-agent-context treats these review actors as Copilot comments:
- exact matches for
copilot-pull-request-reviewer[bot]andgithub-copilot[bot] - regex matches from the built-in
re:copilot.*bot
You can override or extend this with copilot_author_patterns. Exact logins are matched
literally; regex entries must start with re:.
Example:
with:
copilot_author_patterns: |
copilot-pull-request-reviewer[bot]
github-copilot[bot]
re:my-org-copilot-.*Large PRs are budgeted deterministically by section so the prompt remains useful instead of failing hard on size:
- failing job excerpts are reduced first
- broader failing-check excerpts are reduced first
- long review-thread reply bodies are reduced next
- less-important metadata is reduced after that
- uncovered patch line lists are preserved unless the section hits a hard last-resort limit
When truncation happens, the affected item includes a visible note and the debug summary.json
records structured truncation metadata.
The reusable workflow exposes these outputs for downstream observability and automation:
comment_writtencomment_idcomment_urlprompt_sha256unresolved_thread_countfailing_check_countpatch_coverage_percenthas_actionable_items
Cross-matrix coverage merging works best when downstream repos normalize paths in their coverage configuration. Recommended guidance:
- common CLI-only setups such as
pytest --cov=my_pkgandpytest --cov=src/my_pkgare supported automatically, even when the repo does not commit acoverage.pyconfig file - absolute measured paths from split checkouts are rebased onto the active workspace when that can be inferred safely, but explicit normalization is still more reliable
- enable
relative_files = true - define
[paths]mappings when different runners produce different absolute prefixes - keep coverage source configuration explicit when practical
Example pyproject.toml guidance for a downstream repo:
[tool.coverage.run]
relative_files = true
source = ["src"]
[tool.coverage.paths]
source = [
"src",
"/home/runner/work/my-repo/my-repo/src",
"/Users/runner/work/my-repo/my-repo/src",
]- GitHub Actions run/job aggregation requires
actions: read. - External check runs and commit statuses rely on GitHub's checks/status APIs for the PR head SHA.
- When some failing-check APIs are unavailable or partially denied, the tool degrades gracefully:
it keeps the run alive, records warnings in
failing-check-universe.json, and renders whatever useful failing-check context could still be collected. - If
pr-agent-contextitself hits an unexpected internal error, it now degrades gracefully: it logs the traceback, attempts to upsert a managed alert comment with a concise failure summary, and exits successfully so the surrounding CI workflow is not blocked by the reporting step itself.
If a repo uses unusual layout or aggressive path rewriting, pr-agent-context will still merge
the available .coverage* files locally, but checked-in coverage.py config remains the safest
option because it removes any ambiguity from source-scope inference.
Created by Shay Palachy Affek [GitHub]