Skip to content

fix: dedup contract + worker PR contract + conflict-resolver trigger gating#41

Open
norrietaylor wants to merge 5 commits into
mainfrom
fix/bundle-pr-a-dedup-worker-resolver
Open

fix: dedup contract + worker PR contract + conflict-resolver trigger gating#41
norrietaylor wants to merge 5 commits into
mainfrom
fix/bundle-pr-a-dedup-worker-resolver

Conversation

@norrietaylor

Copy link
Copy Markdown
Owner

Summary

Four related chore-contract defects, all in the same PR because they share the dedup/worker-PR/safe-output surface:

  • pr-conflict-resolver detect runs on every PR; should be gated by event_name=push or schedule only #19 — pr-conflict-resolver fires on every PR sync. Dropped the pull_request trigger from the wrapper. Main-side push + 6h scheduled backstop + workflow_dispatch still cover the actual signal (default-branch advance creating new conflicts on open worker PRs). The detect-job's now-unreachable fork-PR if: guard is trimmed.
  • doc-drift dedup ineffective: multiple identical issues filed across re-runs #20 — docs-patrol files duplicate issues on every re-run. The chore prose said "apply the dedup procedure" without specifying how the finding-id slug had to be constructed. Spelled out the marker format (doc-path lowercase relative path; concise-identity a stable per-drift slug like missing-file::<claimed-path>, no run-scoped data) and inlined the dedup procedure with explicit handling for the no-match / one-match / many-match cases.
  • Lint chore dedup update path: identifies existing issue but safe-output rejects update_issue under workflow_dispatch ('not running in issue context') #31 — audit chores' update_issue rejected as "not running in issue context". Audit chores (chore-style-{rust,python,go,toml,ncl}, docs-patrol, dependency-review, test-coverage-detector) all defaulted to update-issue.target: 'triggering', which the gh-aw safe-output runtime only honors when the workflow fires from an issue-event context. These chores run on schedule + dispatch, so every update_issue call silently no-opped. Set target: '*' on each so the agent can pass an explicit issue_number from its dedup search. Tightened the shared idempotency contract in shared/safe-output-create-issue.md to state issue_number is REQUIRED.
  • worker-fix PRs ship without [worker:label] labels and without auto-merge enabled — runbook F3 contract violated #33 — worker-fix PRs ship with no labels and no auto-merge. Mirrored the trivial-dep-bump pattern: declare auto-merge: true + a baseline agent:auto-merge label statically in the workflow (runtime keys auto-merge enablement off the label). The dynamic per-candidate slot label (agent:doc-drift, agent:lint:python, etc.) is added at agent call time via the labels: parameter on the safe-output. Extended the label-classes.yml validator to accept a list of writer globs since agent:auto-merge now has two writer families (trivial-dep-bump-* and worker-fix).

Test plan

  • gh aw compile clean against all 14 workflow sources
  • for src in workflows/*.md; do diff -q "$src" ".github/workflows/$(basename "$src")"; done — mirror clean
  • python3 scripts/audit-wrapper-permissions.py wrappers/*.yml — 14 wrappers, 0 violations
  • python3 scripts/test-safe-output-allowlists.py — allowlists match prose
  • python3 scripts/test-chore-consistency.py — 14 chores consistent across all four surfaces
  • python3 scripts/test-label-classification.py — 18 labels across 14 workflows (after broadening the agent:auto-merge writer glob to a list)
  • actionlint -color clean
  • CI green on PR
  • Manual rehearsal of B4 / F3 against gominimal/spectacles-test to confirm: (a) re-running an audit chore updates the existing finding-issue's updatedAt instead of filing a duplicate, (b) worker-fix PRs carry the issue-slot label + have auto-merge enabled

Closes #19
Closes #20
Closes #31
Closes #33

norrietaylor and others added 4 commits May 21, 2026 07:46
Every PR sync was spinning up the detect job (and surfacing as a PR check)
even when no worker PRs were in conflict. The main-side push + 6h scheduled
backstop already cover the actual signal — a default-branch advance creating
new conflicts on open worker PRs — so the pull_request trigger was pure
overhead and observable latency on high-throughput repos.

The detect-job `if:` guard against fork PRs is no longer reachable; trimmed.

Closes #19

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The docs-patrol chore was filing identical issues across re-runs because
the prose said "apply the dedup procedure" without defining how the
finding-id slug had to be constructed for the same drift to produce a
byte-identical marker on a re-run. The agent was emitting a marker that
varied per run.

Spell out the marker format explicitly: `<doc-path>` is lowercase relative
path, `<concise-identity>` is a stable per-drift slug (e.g.
`missing-file::<claimed-path>`) that MUST NOT include run-scoped data
(timestamps, run ids, SHAs). State the invariant: two re-runs over the
same working tree must produce a byte-identical marker.

Inline the dedup procedure into the chore (rather than delegating via "see
safe-output-create-issue.md") with explicit handling for the three search
outcomes (no match, one match, multiple matches) and a pointer to the MCP
tool name in the allowlist. Set `update-issue.target: '*'` so the chore
can call update_issue from schedule/dispatch contexts; otherwise the
runtime rejects with "not running in issue context" (ch-oracles#31, same
root cause).

Closes #20

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… under workflow_dispatch

Audit chores (chore-style-{rust,python,go,toml,ncl}, dependency-review,
test-coverage-detector) defaulted to `update-issue.target: 'triggering'`,
which the gh-aw safe-output runtime only honors when the workflow itself
fires from an issue-event context. These chores run on schedule and
workflow_dispatch, so every update_issue call was rejected with:

  ✗ Message 1 (update_issue) failed: Target is "triggering" but not
    running in issue context, skipping update_issue

Net effect: the dedup search (find existing finding-issue by finding-id
marker) worked, but the update path silently no-opped — `updatedAt` never
advanced on re-runs.

Set `target: '*'` on each chore's update-issue config so the agent can
pass an explicit issue_number from its dedup search. Tighten the
cross-chore idempotency contract in shared/safe-output-create-issue.md to
state issue_number is REQUIRED (the runtime is not in an issue-event
context and will reject omitted-number calls).

Closes #31

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r PRs

worker-fix was opening PRs whose title started with `[worker:<label>]` but
the PR itself had `labels: []` and `autoMergeRequest: null`. This breaks
the runbook F3 contract: consumer queries like
`gh pr list --label agent:doc-drift` miss every worker PR, and the
auto-merge promise from the chore prose was never honored.

Configure the chore's create-pull-request safe-output the same way
trivial-dep-bump-* does: `auto-merge: true` plus the baseline
`agent:auto-merge` label declared statically in the workflow. The runtime
keys declarative auto-merge enablement off this label.

Spell out in the worker-fix prose that the agent MUST pass an explicit
`labels:` array on the create_pull_request call carrying the candidate
issue's slot label (`agent:lint:python`, `agent:doc-drift`, etc.) — these
are dynamic per candidate and cannot live in the workflow config. The
runtime merges the agent-supplied label with the declarative baseline.

label-classification validator: agent:auto-merge previously had a single
glob writer (`trivial-dep-bump-*`); broaden the schema to accept a list of
globs and extend the auto-merge entry to include `worker-fix`. Update
templates/.github/AGENTS.md to reflect the new writer set.

Closes #33

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

@norrietaylor has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 45 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e31e6bd7-4627-4cca-854b-8e3579b1d049

📥 Commits

Reviewing files that changed from the base of the PR and between 495c508 and b979d61.

📒 Files selected for processing (6)
  • .github/workflows/docs-patrol.lock.yml
  • .github/workflows/docs-patrol.md
  • .github/workflows/worker-fix.lock.yml
  • .github/workflows/worker-fix.md
  • workflows/docs-patrol.md
  • workflows/worker-fix.md
📝 Walkthrough

Walkthrough

This PR enables deduplication and auto-merge for automated chore and worker workflows across multiple GitHub Actions contexts by adding explicit safe-output targeting, hardening finding-id markers, expanding label classification, and removing spurious triggers. Six chore-style workflows plus dependency-review and test-coverage-detector add wildcard targeting to support update_issue in non-issue-event contexts. The docs-patrol workflow hardens finding-id marker format and adds explicit pre-emit dedup procedure. Worker-fix PRs gain baseline auto-merge labels and explicit slot-label requirements. Label classification expands to recognize worker-fix as an auto-merge source. PR conflict resolver removes pull_request trigger to prevent status-check noise.

Changes

Safe-outputs context-targeting for chore workflows

Layer / File(s) Summary
Chore-style and utility workflows: safe-outputs target wildcard
.github/workflows/chore-style-go.lock.yml, .github/workflows/chore-style-go.md, .github/workflows/chore-style-ncl.lock.yml, .github/workflows/chore-style-ncl.md, .github/workflows/chore-style-python.lock.yml, .github/workflows/chore-style-python.md, .github/workflows/chore-style-rust.lock.yml, .github/workflows/chore-style-rust.md, .github/workflows/chore-style-toml.lock.yml, .github/workflows/chore-style-toml.md, .github/workflows/dependency-review.lock.yml, .github/workflows/dependency-review.md, .github/workflows/test-coverage-detector.lock.yml, .github/workflows/test-coverage-detector.md, workflows/chore-style-go.md, workflows/chore-style-ncl.md, workflows/chore-style-python.md, workflows/chore-style-rust.md, workflows/chore-style-toml.md, workflows/dependency-review.md, workflows/test-coverage-detector.md
Lock files regenerate metadata hashes and update update_issue safe-output configuration to include target: "*" in both generated safeoutputs/config.json and GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG. Markdown documentation adds target: '*' setting with comments explaining why schedule/manual dispatch contexts require wildcard targeting instead of issue-event triggering context.

Doc-drift dedup hardening and finding-id marker contract

Layer / File(s) Summary
Docs-patrol lock: finding-id marker hardening and dedup procedure
.github/workflows/docs-patrol.lock.yml
Embedded doc-drift prompt tightens finding-id marker contract: marker must be first body line in exact HTML comment format, with stable byte-identical re-runs per finding. Adds explicit pre-emit dedup algorithm: search open issues by literal marker, then update (single match), create (no match), or update lowest-numbered issue and log duplicates (multiple matches). Safe-outputs adds target: "*" enabling explicit issue-number passing from dedup search.
Docs-patrol markdown: marker spec and dedup procedure documentation
.github/workflows/docs-patrol.md, workflows/docs-patrol.md
Specifies finding-id marker format as required first-line HTML comment with exact fields and byte-identical stability. Expands dedup procedure into explicit step-by-step algorithm with conditional branching for 0/1/N matches, clarifying the required search, emit, and duplicate-handling logic.
Shared safe-output create-issue dedup procedure documentation
shared/safe-output-create-issue.md
Clarifies per-finding dedup idempotency contract: when matching open issue exists, must emit update-issue with explicit issue_number from search and operation=replace. Explains why explicit issue_number is required because audit chores run on schedule/workflow_dispatch without issue-event context.

Worker-fix PR auto-merge and slot-label baseline

Layer / File(s) Summary
Worker-fix lock: PR creation label config and slot-label requirement
.github/workflows/worker-fix.lock.yml
Embedded PR-creation instructions require explicit labels array in create-pull-request containing candidate issue's slot label (with complete switch-table mapping). Safe-output runtime config adds baseline labels: ["agent:auto-merge"] to create_pull_request tool. "What you must not do" section forbids omitting issue-slot label, stating baseline label alone is insufficient.
Worker-fix markdown: PR creation slot-label spec and auto-merge baseline
.github/workflows/worker-fix.md, workflows/worker-fix.md
Specifies PR creation contract requiring explicit labels array with candidate's corresponding slot label from switch table. Explains how baseline agent:auto-merge combines with passed slot label and that omitting the parameter breaks label-query visibility for consumers.

Label classification: agent:auto-merge writer expansion

Layer / File(s) Summary
Label classification and test-label-classification validator
scripts/label-classes.yml, scripts/test-label-classification.py
Label-classes.yml converts agent:auto-merge.writer from single glob (trivial-dep-bump-*) to list of globs including both trivial-dep-bump-* and worker-fix. Test validator updated to parse writer as string or list of strings, validates type, and checks each actual label against the configured glob list. Error messages reference full glob list.
Label documentation: agent:auto-merge sources
templates/.github/AGENTS.md
AGENTS.md documentation updated to reflect that agent:auto-merge applies to PRs from both trivial-dep-bump-* and worker-fix workflows.

PR conflict resolver: remove pull_request trigger

Layer / File(s) Summary
PR conflict resolver: trigger gating and job condition removal
wrappers/pr-conflict-resolver.yml
Removes on.pull_request event trigger, leaving only on.push to main, cron schedule, and workflow_dispatch. Removes jobs.detect.if: condition that previously gated on github.event_name, allowing detect job to run unconditionally under the remaining triggers.

🎯 3 (Moderate) | ⏱️ ~20 minutes


🐰 The workflows now find their findings with care,
Dedup markers stable, duplicates don't dare,
Auto-merge labels flow through worker-fix PRs,
And conflict resolvers skip unrelated stirs.
Context-aware targets help chores dance in place,
No matter when they run in time or space.


  • norrietaylor/ch-oracles#9: Extends the label-classification schema introduced in this PR by supporting writer as either a string or list of strings, enabling multiple workflows to be flagged as label writers.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main changes: fixing dedup contract for docs-patrol, worker PR contracts for auto-merge/labels, and conflict-resolver trigger gating.
Description check ✅ Passed The PR description comprehensively covers all four linked issues (#19, #20, #31, #33), explains the changes made, and provides a detailed test plan.
Linked Issues check ✅ Passed The PR addresses all four linked issues: #19 (removed pull_request trigger), #20 (specified finding-id marker format and dedup procedure), #31 (changed update-issue target to '*'), #33 (added auto-merge and labels to worker-fix).
Out of Scope Changes check ✅ Passed All changes are scoped to the four linked issues: workflow trigger changes, safe-output configurations, label classifications, dedup procedures, and worker PR metadata.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bundle-pr-a-dedup-worker-resolver

Comment @coderabbitai help to get the list of available commands and usage tips.

@norrietaylor

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/docs-patrol.md (1)

141-147: ⚡ Quick win

Use one placeholder name for the dedup slug across marker and query.

Line 143 defines <concise-identity>, but Line 169 switches to <identity>. Keep this token name identical to reduce agent ambiguity when constructing/searching the marker.

Suggested edit
-   query `is:issue is:open label:agent:doc-drift "finding-id: doc-drift::n-a::<doc-path>::<identity>" in:body`.
+   query `is:issue is:open label:agent:doc-drift "finding-id: doc-drift::n-a::<doc-path>::<concise-identity>" in:body`.

Also applies to: 167-170

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docs-patrol.md around lines 141 - 147, The doc uses two
different placeholder names for the deduplication slug (<concise-identity> vs
<identity>), which will confuse agents; pick one token (preferably
<concise-identity>) and replace every instance of the other token so the marker
and query use the identical placeholder for the dedup slug (update the
definition under the dedup/slug section and the later example/stale-command
block where the alternate <identity> appears). Ensure the chosen token is used
consistently in the examples and the slug-format sentence describing allowed
characters.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/worker-fix.lock.yml:
- Line 1509: The Safe Outputs config currently sets create_pull_request.draft as
a string ("${{ false }}") which violates the workflow schema expecting a
boolean; update the create_pull_request.draft entry used in the generated
safeoutputs/config.json and the GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG JSON to be an
actual JSON boolean (draft: false) instead of a quoted string, ensuring the
create_pull_request object remains valid and matches the schema for draft:type
boolean.

---

Nitpick comments:
In @.github/workflows/docs-patrol.md:
- Around line 141-147: The doc uses two different placeholder names for the
deduplication slug (<concise-identity> vs <identity>), which will confuse
agents; pick one token (preferably <concise-identity>) and replace every
instance of the other token so the marker and query use the identical
placeholder for the dedup slug (update the definition under the dedup/slug
section and the later example/stale-command block where the alternate <identity>
appears). Ensure the chosen token is used consistently in the examples and the
slug-format sentence describing allowed characters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ac00e1dd-d2fd-49ce-beda-7f1de0396b5e

📥 Commits

Reviewing files that changed from the base of the PR and between 62a687e and 495c508.

📒 Files selected for processing (32)
  • .github/workflows/chore-style-go.lock.yml
  • .github/workflows/chore-style-go.md
  • .github/workflows/chore-style-ncl.lock.yml
  • .github/workflows/chore-style-ncl.md
  • .github/workflows/chore-style-python.lock.yml
  • .github/workflows/chore-style-python.md
  • .github/workflows/chore-style-rust.lock.yml
  • .github/workflows/chore-style-rust.md
  • .github/workflows/chore-style-toml.lock.yml
  • .github/workflows/chore-style-toml.md
  • .github/workflows/dependency-review.lock.yml
  • .github/workflows/dependency-review.md
  • .github/workflows/docs-patrol.lock.yml
  • .github/workflows/docs-patrol.md
  • .github/workflows/test-coverage-detector.lock.yml
  • .github/workflows/test-coverage-detector.md
  • .github/workflows/worker-fix.lock.yml
  • .github/workflows/worker-fix.md
  • scripts/label-classes.yml
  • scripts/test-label-classification.py
  • shared/safe-output-create-issue.md
  • templates/.github/AGENTS.md
  • workflows/chore-style-go.md
  • workflows/chore-style-ncl.md
  • workflows/chore-style-python.md
  • workflows/chore-style-rust.md
  • workflows/chore-style-toml.md
  • workflows/dependency-review.md
  • workflows/docs-patrol.md
  • workflows/test-coverage-detector.md
  • workflows/worker-fix.md
  • wrappers/pr-conflict-resolver.yml

Comment thread .github/workflows/worker-fix.lock.yml Outdated
Addresses CodeRabbit feedback on PR #41.

- worker-fix.md: draft is now literal false (was ${{ false }} which gh-aw renders as the string "${{ false }}" in the safe-outputs config JSON — schema expects boolean).
- docs-patrol.md: rename <identity> to <concise-identity> in the dedup search query so the placeholder name matches the marker definition higher in the file.
@norrietaylor

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment