Repository navigation
feat: implement issue #1713 — [#1269 split 1/2] shadow_mode input + total PR-output suppression (inert, nothing dispatched) #1730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
25ebaf3
1b46a54
a810206
c763258
a6a88be
feb84ab
fcb1087
f04e708
a45948d
dd1be7d
2919be9
64153e7
4f6af5b
97abe44
6ced358
a2d4c41
f64a605
92b5981
dde0b5a
fce2ad1
8840829
5e3be1f
5786dcd
8336b94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,14 +9,24 @@ on: | |
| required: false | ||
| default: "" | ||
| agent_ref: | ||
| # Ref of petry-projects/.github-private to check out for the dev-lead | ||
| # Ref of combsmith/.github-private to check out for the dev-lead | ||
| # scripts. A trigger-stub caller passes its pinned channel (dev-lead/stable) | ||
| # so the agent runs at the pinned version, not main — the self-host pin | ||
| # (#535/#506). Defaults to "main" → unchanged behaviour. | ||
| description: "Ref of petry-projects/.github-private to check out for dev-lead scripts" | ||
| description: "Ref of combsmith/.github-private to check out for dev-lead scripts" | ||
| type: string | ||
| required: false | ||
| default: "main" | ||
| shadow_mode: | ||
| # #1713 (split 1/2): when true, this lane posts NOTHING to the PR/issue | ||
| # (no review, comment, thread reply, label, or auto-merge enable) — its | ||
| # output goes to the run log + a file instead. Default false ⇒ behaviour | ||
| # is byte-identical to today. INERT: only an explicit caller input can | ||
| # set it; nothing dispatches a shadow lane yet (that is split 2/2). | ||
| description: "Run silently: suppress all PR/issue output (shadow mode)" | ||
| type: boolean | ||
| required: false | ||
| default: false | ||
| secrets: | ||
| CLAUDE_CODE_OAUTH_TOKEN: | ||
| # required:false (#1525) — a hard required:true fails the CALLER at | ||
|
|
@@ -36,8 +46,8 @@ on: | |
| # callers that haven't set the new secret yet still authenticate. Remove once | ||
| # GH_PAT_DON_PETRY is confirmed everywhere. | ||
| # Required for cross-repo callers: the default github.token cannot read | ||
| # petry-projects/.github-private from another repo. Callers must pass a | ||
| # PAT that has read access to petry-projects/.github-private. | ||
| # combsmith/.github-private from another repo. Callers must pass a | ||
| # PAT that has read access to combsmith/.github-private. | ||
| required: false | ||
| GOOGLE_API_KEY: | ||
| required: false | ||
|
|
@@ -58,7 +68,7 @@ permissions: {} | |
| # and can no longer drift into — their own concurrency group. Separate per-issue | ||
| # and per-PR lanes ensure issue pickups are NEVER cancelled by unrelated PR | ||
| # follow-up traffic (reviews, comments, ci-relay) — the root cause of issue | ||
| # pickups being cancelled before they could open a PR. See petry-projects/.github#402. | ||
| # pickups being cancelled before they could open a PR. See combsmith/.github#402. | ||
| # | ||
| # Routing (first match wins): | ||
| # check_run -> dev-lead-ci-relay-<sha> (per-SHA relay lane) | ||
|
|
@@ -168,6 +178,11 @@ jobs: | |
| AI_MODELS_GEMINI: ${{ vars.AI_MODELS_GEMINI || '' }} | ||
| AI_MODELS_COPILOT: ${{ vars.AI_MODELS_COPILOT || '' }} | ||
| DEV_LEAD_DRY_RUN: ${{ vars.DEV_LEAD_DRY_RUN || 'false' }} | ||
| # #1713 (split 1/2): forwarded to the handlers, which fail-closed suppress | ||
| # ALL PR/issue output when active. Sourced from the caller's shadow_mode | ||
| # input (default false ⇒ today's behaviour). Nothing sets it yet. | ||
| DEV_LEAD_SHADOW_MODE: ${{ inputs.shadow_mode }} | ||
| SHADOW_OUTPUT_FILE: /tmp/dev-lead-shadow-output-${{ github.run_id }}.txt | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Running |
||
| DEEP_TIMEOUT_SEC: ${{ vars.DEEP_TIMEOUT_SEC || '' }} | ||
| ACTION_TIMEOUT_SEC: ${{ vars.ACTION_TIMEOUT_SEC || '' }} | ||
| GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS || github.token }} | ||
|
|
@@ -227,7 +242,7 @@ jobs: | |
| - name: Checkout dev-lead scripts | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| repository: petry-projects/.github-private | ||
| repository: combsmith/.github-private | ||
| # Pinned channel (e.g. dev-lead/stable) when a trigger stub passes it; | ||
| # defaults to "main" → unchanged behaviour (#535/#506). | ||
| ref: ${{ inputs.agent_ref }} | ||
|
|
@@ -800,7 +815,7 @@ jobs: | |
| - name: Checkout dev-lead scripts | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| repository: petry-projects/.github-private | ||
| repository: combsmith/.github-private | ||
| # Pinned channel (e.g. dev-lead/stable) when a trigger stub passes it; | ||
| # defaults to "main" → unchanged behaviour (#535/#506). | ||
| ref: ${{ inputs.agent_ref }} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,15 @@ source "$(dirname "$0")/lib/pr-automation-budget.sh" | |
| # missing 3+ required description sections, once, marker-keyed. | ||
| source "$(dirname "$0")/lib/dev-lead-pr-body.sh" | ||
|
|
||
| # shadow_mode_active / shadow_apply_suppression (#1713): total PR-output | ||
| # suppression when this lane runs in shadow mode. | ||
| source "$(dirname "$0")/lib/shadow-suppress.sh" | ||
|
|
||
| # In shadow mode, force the already-tested dry-run "post nothing" path so no | ||
| # PR output escapes. Must run before any posting site. | ||
| DEV_LEAD_DRY_RUN="${DEV_LEAD_DRY_RUN:-false}" | ||
| shadow_apply_suppression | ||
|
|
||
| PR_NUMBER="${PR_NUMBER:-}" | ||
| HEAD_SHA="${HEAD_SHA:-}" | ||
| CHECKS_JSON="${CHECKS_JSON:-[]}" | ||
|
|
@@ -82,6 +91,12 @@ has_rate_limited_marker() { | |
| [ "${count:-0}" -gt 0 ] | ||
| } | ||
|
|
||
| # collect_logs: fetch logs from a failing GitHub Actions run, or the PR diff if an | ||
| # external quality gate (SonarCloud, etc). Exports details for the fix-ci prompt. | ||
| # | ||
| # Arguments: | ||
| # $1 check_name - name of the failing check (for reference in output) | ||
| # $2 details_url - URL to the check details/run | ||
| collect_logs() { | ||
| local check_name="$1" details_url="$2" | ||
| local run_id | ||
|
|
@@ -98,6 +113,11 @@ collect_logs() { | |
| fi | ||
| } | ||
|
|
||
| # build_prompt: render the fix-ci prompt template with check context, failure logs, | ||
| # and PR annotations. Writes the rendered prompt to a temp file and echoes its path. | ||
| # | ||
| # Returns: | ||
| # Path to the rendered prompt file | ||
| build_prompt() { | ||
| local prompt_template="${PROMPTS_DIR}/fix-ci.md" | ||
| local check_name app_slug details_url | ||
|
|
@@ -119,6 +139,12 @@ build_prompt() { | |
| echo "$rendered" | ||
| } | ||
|
|
||
| # post_summary: post a dev-lead fix-ci status comment to the PR. Embeds a marker | ||
| # with the commit SHA and status for dedup and tracking. Logs instead when DEV_LEAD_DRY_RUN=true. | ||
| # | ||
| # Arguments: | ||
| # $1 status - status label (e.g. "applied", "failed", "rate-limited", "dry-run") | ||
| # $2 details - optional details/reason to append to the comment body | ||
| post_summary() { | ||
| local status="$1" details="${2:-}" | ||
| local marker="${MARKER_PREFIX}${HEAD_SHA} status=${status} -->" | ||
|
|
@@ -175,6 +201,11 @@ ${details}" | |
| fi | ||
| } | ||
|
|
||
| # post_exhaustion: post a dev-lead exhaustion marker when the fix-ci handler has failed | ||
| # MAX_FAIL_ATTEMPTS times. Includes the reason for the last failure and instructions for recovery. | ||
| # | ||
| # Arguments: | ||
| # $1 reason - the reason for the final engine failure (timeout, error, rate-limit) | ||
| post_exhaustion() { | ||
| local reason="$1" | ||
| local body="${EXHAUSTION_MARKER} | ||
|
|
@@ -230,7 +261,7 @@ main() { | |
| local prompt_file | ||
| prompt_file=$(build_prompt) | ||
|
|
||
| if [ "${DEV_LEAD_DRY_RUN:-false}" = "true" ]; then | ||
| if [ "${DEV_LEAD_DRY_RUN:-false}" = "true" ] && ! shadow_mode_active; then | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bypassing the dry-run early exit when |
||
| echo "[dry-run] fix-ci: would run engine with prompt: $prompt_file" | ||
| post_summary "dry-run" "Would apply fix for: $(echo "$CHECKS_JSON" | jq -r '[.[].name] | join(", ")')" | ||
| exit 0 | ||
|
|
@@ -250,7 +281,12 @@ main() { | |
| echo " [fix-ci] cycle $cycle/$MAX_CI_CYCLES" | ||
|
|
||
| local engine_rc=0 | ||
| run_writer_with_fallback "$prompt_file" "fix-ci" || engine_rc=$? | ||
| local capture_file="${SHADOW_OUTPUT_FILE:-}" | ||
| if [ -n "$capture_file" ]; then | ||
| run_writer_with_fallback "$prompt_file" "fix-ci" 2>&1 | tee -a "$capture_file" || engine_rc=$? | ||
| else | ||
| run_writer_with_fallback "$prompt_file" "fix-ci" || engine_rc=$? | ||
| fi | ||
|
|
||
| if [ "$engine_rc" -ne 0 ]; then | ||
| if [ "$engine_rc" -eq 2 ]; then | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,10 +19,16 @@ source "$(dirname "$0")/lib/issue-comments.sh" | |
| # net_diff_is_empty / net_diff_summary (#1786): refuse to open/claim a PR whose | ||
| # three-dot net diff against base is empty. | ||
| source "$(dirname "$0")/lib/net-diff-guard.sh" | ||
| # shadow_mode_active / shadow_apply_suppression (#1713): total PR-output | ||
| # suppression when this lane runs in shadow mode. | ||
| source "$(dirname "$0")/lib/shadow-suppress.sh" | ||
|
|
||
| ISSUE_NUMBER="${ISSUE_NUMBER:-}" | ||
| REPO="${REPO:-${GITHUB_REPOSITORY:-}}" | ||
| DEV_LEAD_DRY_RUN="${DEV_LEAD_DRY_RUN:-false}" | ||
| # In shadow mode, force the already-tested dry-run "post nothing" path so no | ||
| # PR/issue output escapes. Must run before any posting site. | ||
| shadow_apply_suppression | ||
| export PROMPTS_DIR="${PROMPTS_DIR:-prompts/dev-lead}" | ||
|
|
||
| # Machine-readable marker the retry cron (dev-lead-retry.sh) scans for to requeue | ||
|
|
@@ -45,11 +51,11 @@ NEEDS_HUMAN_LABEL="${NEEDS_HUMAN_LABEL:-dev-lead:needs-human}" | |
| # from a completed pass at a glance. It is deliberately never a completion claim. | ||
| CHECKPOINT_COMMIT_PREFIX="${CHECKPOINT_COMMIT_PREFIX:-checkpoint(dev-lead): INCOMPLETE —}" | ||
|
|
||
| # PR-limit admission gate (epic petry-projects/.github#505 Phase 3) config. | ||
| # PR-limit admission gate (epic combsmith/.github#505 Phase 3) config. | ||
| # The shared guard + limits config live in the PUBLIC standards repo so every | ||
| # automation caller enforces the same org-wide cap. PLG_STANDARDS_DIR lets tests | ||
| # and offline runs source a local checkout instead of hitting the API. | ||
| PLG_SOURCE_REPO="${PLG_SOURCE_REPO:-petry-projects/.github}" | ||
| PLG_SOURCE_REPO="${PLG_SOURCE_REPO:-combsmith/.github}" | ||
| PLG_SOURCE_REF="${PLG_SOURCE_REF:-main}" | ||
| PLG_DEFER_MARKER="<!-- dev-lead-issue-deferred -->" | ||
|
|
||
|
|
@@ -555,8 +561,14 @@ main() { | |
|
|
||
| if check_existing_pr; then | ||
| echo "::notice::Existing open PR found for issue #${ISSUE_NUMBER} — skipping (dedup)" | ||
| gh issue comment "$ISSUE_NUMBER" --repo "$REPO" \ | ||
| --body "<!-- dev-lead-issue-dedup -->Already working on this: an open PR exists for issue #${ISSUE_NUMBER}." 2>/dev/null || true | ||
| # Dedup comment fires before the dry-run early-exit below, so it is the one | ||
| # posting site not covered by forced dry-run — gate it on shadow or dry-run. | ||
| if shadow_mode_active || [ "${DEV_LEAD_DRY_RUN:-false}" = "true" ]; then | ||
| echo "[shadow/dry-run] would post dedup comment for issue #${ISSUE_NUMBER} (suppressed)" | ||
| else | ||
| gh issue comment "$ISSUE_NUMBER" --repo "$REPO" \ | ||
| --body "<!-- dev-lead-issue-dedup -->Already working on this: an open PR exists for issue #${ISSUE_NUMBER}." 2>/dev/null || true | ||
| fi | ||
| exit 0 | ||
| fi | ||
|
|
||
|
|
@@ -590,15 +602,15 @@ main() { | |
| envsubst < "$template_path" > "$prompt_file" | ||
| fi | ||
|
|
||
| if [ "$DEV_LEAD_DRY_RUN" = "true" ]; then | ||
| if [ "$DEV_LEAD_DRY_RUN" = "true" ] && ! shadow_mode_active; then | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding Nothing after |
||
| echo "[dry-run] fix-issue: would implement issue #${ISSUE_NUMBER} using prompt: $prompt_file" | ||
| rm -f "${prompt_file:-}" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # PR-limit admission gate — epic petry-projects/.github#505 Phase 3. | ||
| # PR-limit admission gate — epic combsmith/.github#505 Phase 3. | ||
| # Before opening a new automation PR, defer if the org-wide open-automation-PR | ||
| # queue is at the signed-off cap (standards/pr-limits.json in petry-projects/.github). | ||
| # queue is at the signed-off cap (standards/pr-limits.json in combsmith/.github). | ||
| # Placed AFTER the dry-run early-exit so a dry-run never defers; FAIL-OPEN so a | ||
| # guard problem never blocks PR creation. | ||
| if ! admission_gate_allows; then | ||
|
|
@@ -620,7 +632,12 @@ main() { | |
| pre_engine_sha=$(git rev-parse HEAD) | ||
|
|
||
| local engine_rc=0 | ||
| run_writer_with_fallback "$prompt_file" "fix-issue" || engine_rc=$? | ||
| local capture_file="${SHADOW_OUTPUT_FILE:-}" | ||
| if [ -n "$capture_file" ]; then | ||
| run_writer_with_fallback "$prompt_file" "fix-issue" 2>&1 | tee -a "$capture_file" || engine_rc=$? | ||
| else | ||
| run_writer_with_fallback "$prompt_file" "fix-issue" || engine_rc=$? | ||
| fi | ||
| if [ "$engine_rc" -ne 0 ]; then | ||
| # Unified failure handler: classifies the cause, surfaces it on the issue | ||
| # (marker + comment + run link + redacted snippet), and decides retry vs. | ||
|
|
@@ -709,7 +726,7 @@ ${lint_output} | |
| rm -f "$pr_body_file" | ||
| echo "$pr_url" | ||
|
|
||
| # Mark the PR auto-rebase-eligible from creation (petry-projects/.github#711). | ||
| # Mark the PR auto-rebase-eligible from creation (combsmith/.github#711). | ||
| # The auto-rebase 'review-ready' gate (#465) only rebases PRs that are approved | ||
| # OR carry the ready label; without this, a dev-lead PR that falls behind before | ||
| # it is approved is skipped, drifts into a merge conflict, and cannot be approved | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -63,11 +63,18 @@ source "$(dirname "$0")/lib/ci-status.sh" | |
| # labels API, logs the API's message on failure, and a failed hold fails the run. | ||
| source "$(dirname "$0")/lib/hold-label.sh" | ||
|
|
||
| # shadow_mode_active / shadow_apply_suppression (#1713): total PR-output | ||
| # suppression when this lane runs in shadow mode. | ||
| source "$(dirname "$0")/lib/shadow-suppress.sh" | ||
|
|
||
| INTENT_TYPE="${INTENT_TYPE:-fix-reviews}" | ||
| PR_NUMBER="${PR_NUMBER:-}" | ||
| REPO="${REPO:-${GITHUB_REPOSITORY:-}}" | ||
| HEAD_SHA="${HEAD_SHA:-}" | ||
| DEV_LEAD_DRY_RUN="${DEV_LEAD_DRY_RUN:-false}" | ||
| # In shadow mode, force the already-tested dry-run "post nothing" path so no PR | ||
| # output escapes. Must run before the budget/hold/dispatch posting sites below. | ||
| shadow_apply_suppression | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling |
||
| export PROMPTS_DIR="${PROMPTS_DIR:-prompts/dev-lead}" | ||
| # Pin PROMPTS_DIR to an absolute path now, while CWD still points at the agent | ||
| # checkout — checkout_pr_in_worktree cds into the PR worktree, after which a | ||
|
|
@@ -319,7 +326,7 @@ build_and_run() { | |
| printf '%s\n' "${out}${rest}" > "$prompt_file" | ||
| fi | ||
|
|
||
| if [ "$DEV_LEAD_DRY_RUN" = "true" ]; then | ||
| if [ "$DEV_LEAD_DRY_RUN" = "true" ] && ! shadow_mode_active; then | ||
| echo "[dry-run] would run engine with prompt: $prompt_file ($(wc -l < "$prompt_file") lines)" | ||
| rm -f "${prompt_file:-}" | ||
| return 0 | ||
|
|
@@ -335,7 +342,12 @@ build_and_run() { | |
| echo "::warning::could not install the reply recorder — no-change dispositions are disabled this pass (#2079)" | ||
| [ -n "${PASS_REPLY_RECORD:-}" ] && printf 'UNATTRIBUTED\n' > "$PASS_REPLY_RECORD" | ||
| fi | ||
| PATH="$engine_path" run_writer_with_fallback "$prompt_file" "${INTENT_TYPE:-}" || rc=$? | ||
| local capture_file="${SHADOW_OUTPUT_FILE:-}" | ||
| if [ -n "$capture_file" ]; then | ||
| PATH="$engine_path" run_writer_with_fallback "$prompt_file" "${INTENT_TYPE:-}" 2>&1 | tee -a "$capture_file" || rc=$? | ||
| else | ||
| PATH="$engine_path" run_writer_with_fallback "$prompt_file" "${INTENT_TYPE:-}" || rc=$? | ||
| fi | ||
| [ -n "$shim_dir" ] && rm -rf "$shim_dir" | ||
| rm -f "${prompt_file:-}" | ||
| return "$rc" | ||
|
|
@@ -1139,7 +1151,7 @@ resolve_addressed_bot_threads() { | |
| fi | ||
| # #2013: the claimed commit must be one THIS pass produced — not the PR's first | ||
| # commit or any other pre-pass ancestor, which passes the on-head + intersection | ||
| # checks trivially (the petry-projects/.github#1220 `571a3b8` case). Parity with | ||
| # checks trivially (the combsmith/.github#1220 `571a3b8` case). Parity with | ||
| # the issue-comment `fixed` check in resolve_dispositioned_comments (#1813). | ||
| in_base=$(printf '%s' "$facts" | jq -r 'if .in_base == false then "false" else "true" end' 2>/dev/null || echo "true") | ||
| if ! in_pass=$(acv_claim_in_pass "${RESOLUTION_BASE_SHA:-}" "$on_head" "$in_base"); then | ||
|
|
@@ -3036,7 +3048,7 @@ commit_and_push() { | |
| ;; | ||
| esac | ||
| # Sync-stub guard (#2185): on a `standards-sync` PR, a bot-driven pass must not | ||
| # change a stub copied verbatim from petry-projects/.github/standards/ (the | ||
| # change a stub copied verbatim from combsmith/.github/standards/ (the | ||
| # incubator#164 shape). Restoring a stub to what the sync commit wrote is | ||
| # allowed. Maintainer-driven intents (review-changes/human-pr, on-mention, | ||
| # human) are the explicit override and are not guarded. The PR is read only | ||
|
|
@@ -3063,7 +3075,7 @@ commit_and_push() { | |
| ;; | ||
| esac | ||
| # Test-tamper guard (#2013): a bot-driven fix pass must not silently rewrite an | ||
| # existing test to make its own change pass (the petry-projects/.github#1220 | ||
| # existing test to make its own change pass (the combsmith/.github#1220 | ||
| # `13927fc` shape). Refuse the push and escalate, like the no-op guard (rc 3). | ||
| # "Existing" means present at the merge base with the PR's base branch (#2141): | ||
| # tests the PR itself added are free to revise. The merge base is resolved only | ||
|
|
@@ -3416,7 +3428,7 @@ If the test really is wrong, a human should make that call, or the fix should ca | |
| # flag_sync_stub_drift <intent> <rows> — the sync-stub guard refused the push | ||
| # (#2185). <rows> are `path\ttemplate` lines (empty when the scan was unknown). | ||
| # Same escalation as flag_test_tamper; the comment answers the bot suggestion as | ||
| # declined and points at each template in petry-projects/.github. | ||
| # declined and points at each template in combsmith/.github. | ||
| flag_sync_stub_drift() { | ||
| local intent="$1" rows="$2" | ||
| _AM_NEEDS_RESTORE=0 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.