Track main rather than a release tag - #42
Conversation
`@v0` resolved to a commit from before the Python rewrite — 22 behind main — so both content-repo workflows were running the R tooling. The Pydantic validator merged in #39 was validating nothing, and CI was green throughout. That is the failure mode of a moving tag nobody moves: silent, not loud. The tag exists to decouple content repositories from a release cadence, which assumes consumers outside the org's control. There is one content repository, the same person owns both, and nothing external consumes either — so the tag was not decoupling anyone, it was a pointer someone had to remember to move. More than an expedient, though. Pinning is the ordinary answer for a library, where freezing a dependency is reasonable. It is a stranger answer for a standard, where conforming to last year's version is not obviously conformance. A node validating against a snapshot enforces rules the standard no longer publishes, and reports success while doing it. The README says this is a live question rather than settled, and asks a node that needs to pin to say why — the answer should come from what federated nodes actually need, not from a default chosen before any existed. 77 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved workflow trigger, security, and documentation/ADR issues remain.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Updates template workflows to track tooling from @main instead of @v0, with revised guidance on SHA pinning.
Changes:
- Updates validation and index-generation action references.
- Documents live tracking rationale and immutable SHA alternatives.
File summaries
| File | Review findings |
|---|---|
template/.github/workflows/validate.yml |
Nit (1 vote): The workflow conflicts with ADR 0011; amend or supersede the ADR and release guidance. |
template/.github/workflows/generate-index.yml |
Moderate (2 votes): No trigger regenerates the index when standard tooling changes. Critical (1 vote): Mutable @main executes with write access; retain an immutable default or controlled SHA updates. |
README.md |
Nit (3 votes): Earlier guidance and ADR 0011 still recommend release tags/@v0; reconcile or explicitly scope this policy. |
Review details
Suppressed comments (1)
template/.github/workflows/validate.yml:16
- This changes the template away from the accepted decision in
docs/adr/0011-pre-release-tags-for-the-tooling.md:40-42,59-61, which says consumers must use@v0and that the template starts with that reference. Please amend or supersede that ADR and its release guidance with this policy change; otherwise the repository retains an authoritative decision that contradicts this workflow.
- uses: waldronlab/agent-protocol-standard/actions/validate-protocols@main
- Files reviewed: 3/3 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # index that disagrees with the standard it claims to follow, and does so silently — CI stays | ||
| # green while running the wrong code. Tracking `main` means a breaking change reaches you | ||
| # loudly instead, which is the trade this project wants while the spec is 0.y.z. | ||
| - uses: waldronlab/agent-protocol-standard/actions/generate-index@main |
There was a problem hiding this comment.
🟡 Changes recommended
Resolve the README/ADR inconsistencies, workflow rationale contradiction, and mutable-main write-permission concern.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
README.md:92
- This new guidance conflicts with the repository's remaining release guidance:
README.md:39still tells adopters to pin both actions to a release tag,README.md:118-125still presents@v0as the moving tag, and ADR 0011:59-61 says consumers must reference@v0. The result is contradictory instructions about whether@main,@v0, or a release tag is canonical. Please update/remove those legacy statements (and the ADR if this is the new policy), or explicitly scope this paragraph to the template default.
This is a live question rather than a settled one. Pinning is the ordinary answer for a *library*,
where a consumer reasonably freezes a dependency; it is a stranger answer for a *standard*, where
conforming to last year's version is not obviously conformance at all. If you have a reason to pin —
a node you cannot update promptly, say — pin a commit SHA rather than a tag, since a tag can itself
be retargeted, and tell us, because the answer should be driven by what federated nodes actually
template/.github/workflows/generate-index.yml:57
- This action now resolves from mutable
mainwhile the job grantscontents: write(generate-index.yml:16-17), so any standard-main change—including a compromise or accidental push—can run arbitrary generator code and commit to the content repository. Either pin this write-capable action to a full SHA or change the write path to a reviewed PR and explicitly accept the resulting tradeoff.
- uses: waldronlab/agent-protocol-standard/actions/generate-index@main
template/.github/workflows/validate.yml:16
- This changes the shipped template contrary to accepted ADR-0011, which says consumers must reference
@v0and new repositories start on it; ADR-0006 also still specifies release-tag consumption. Please add a superseding/amending ADR (and update its index) or keep the workflow and normative docs consistent.
- uses: waldronlab/agent-protocol-standard/actions/validate-protocols@main
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
…lize runs The README said three different things at once. Line 39 still told adopters to pin to a release tag, the Releasing section still presented `v0` as the distribution channel, and the new paragraph said `@main`. Releases are now described as a record rather than a channel, with `v0` kept for anyone who has a reason to pin. ADR 0011's consequence that "consumers must reference `@v0`" no longer describes the shipped template, so it carries a banner saying so. Not superseded: whether a node should ever pin is deliberately still open, and settling it warrants an ADR against both 0011 and 0006 §3. The template workflow's own header still said the generator was "pinned below" while the step beneath it resolved `@main` — the file contradicted itself. The committer email was wrong: it used the App registration id from INDEX_APP_ID, where the address needs the bot's *user* id. Verified against the API — the registration is 4936017, the bot user is 328912240. With the wrong value the push still succeeds and GitHub silently declines to associate the commit with the bot, which is the kind of failure nobody notices. Both workflows now resolve it at run time, so the template works for any adopter's App. Two merges in quick succession would have run concurrently, and the second push would have been rejected as non-fast-forward, its checkout predating the first run's index commit. A concurrency group queues them; cancel-in-progress stays false because a cancelled run leaves the index describing the previous commit. 77 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The index-generation workflow has unresolved push-safety, stale-output, and silent attribution failures; the README also has two documentation nits.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (5)
README.md:83
- The new README link points at an anchor that is never created: this text is a bold paragraph, not a Markdown heading, so GitHub will not generate
#the-template-references-main-not-a-release-tag-and-that-is-deliberate. Make this line a heading (or point the link at an existing heading) so the navigation works.
**The template references `@main`, not a release tag, and that is deliberate.** A node validating
README.md:125
v0is the moving major tag (this file says it moves on each release), so it is not a pin. As written, this contradicts the preceding instruction to pin a commit SHA and could send consumers to a mutable ref; distinguish the versionedv0.x.yrelease tags from the movingv0tag.
`@main`, so nothing is waiting on a tag to move. `v0` is kept for anyone who has a reason to pin, and
because the drift it accumulates is a useful signal of how far behind a pinned consumer would be.
template/.github/workflows/generate-index.yml:79
- The public
committer-emailinput still documents the prefix as<app-id>+...inactions/generate-index/action.yml:29-30, while this workflow now correctly supplies the bot user ID and explicitly says the App registration ID is wrong. Update that action metadata/documentation with this change; otherwise other callers will follow the documented contract and lose GitHub bot attribution.
committer-email: ${{ steps.bot.outputs.id && format('{0}+{1}[bot]@users.noreply.github.com', steps.bot.outputs.id, steps.app-token.outputs.app-slug) || 'github-actions[bot]@users.noreply.github.com' }}
template/.github/workflows/generate-index.yml:74
- Switching this reference to
@mainonly affects the next execution. The workflow still triggers only for localprotocols/**or workflow-file changes, so a generator change onagent-protocol-standardcan leavePROTOCOLS.yamlpublished from the old generator indefinitely; that contradicts the claim that trackingmainprevents stale generated output. Add a standard-to-node dispatch/scheduled reconciliation, or document an explicit regeneration trigger for generator changes.
# Deliberately @main rather than a release tag. A generator that has fallen behind produces an
# index that disagrees with the standard it claims to follow, and does so silently — CI stays
# green while running the wrong code. Tracking `main` means a breaking change reaches you
# loudly instead, which is the trade this project wants while the spec is 0.y.z.
- uses: waldronlab/agent-protocol-standard/actions/generate-index@main
template/.github/workflows/generate-index.yml:68
- Because the lookup is embedded in
echoand the step has noset -e, a failedgh apistill leaves the step successful and writes an empty id. With the App configured, line 79 then silently selects thegithub-actions[bot]fallback even though the App token performs the push, losing the intended attribution. Capture and validate the lookup and fail on error instead of allowing the no-App fallback in this path.
run: echo "id=$(gh api "/users/${SLUG}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
| # rejected as non-fast-forward: its checkout predates the first run's index commit. Queue rather | ||
| # than cancel — a cancelled run leaves the index describing the previous commit. | ||
| concurrency: | ||
| group: generate-index | ||
| cancel-in-progress: false |
…tract `actions/generate-index`'s own `committer-email` documentation still said the prefix is the App registration id, which is the thing the last commit fixed in the workflows. The input description is the public contract, so a caller following it would have lost bot attribution exactly as this repository did. The concurrency group alone did not make the push safe: `actions/checkout` defaults to the event SHA, so a queued run still started from the commit that triggered it and pushed a non-fast-forward once `main` had moved. Both the template and the content repository now check out the branch tip. `echo "id=$(gh api ...)"` exits 0 when the lookup fails, writing an empty id and falling back to github-actions[bot] while the App did the push. Assigned under `set -euo pipefail`, with empty failing the step. Added `workflow_dispatch`: tracking the generator at `@main` means the next run uses it, but a generator change upstream raises no event in a content repository, so the index can sit stale until an unrelated local change. Named `schedule:` as the fuller answer rather than pretending the gap is closed. Two README errors of mine: the link pointed at an anchor that never existed, because bold text is not a heading — it is a heading now; and it called `v0` something to pin to, two lines after advising a commit SHA, when `v0` moves on every release and is no more fixed than `main`. The immutable `v0.x.y` tags are what that sentence meant. 77 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Workflow consistency and documentation issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
docs/adr/0011-pre-release-tags-for-the-tooling.md:8
- This amendment only qualifies ADR-0011's old consequence, but ADR-0006 §3 still states that consumers consume these actions pinned to a release tag. With the shipped template now using
@main, the accepted architecture record is contradictory; please add an amendment note to ADR-0006 (even if the broader pinning question remains open) or explicitly mark that sentence as historical.
> **Amended by the template's move to `@main` (2026-09-14).** The consequence below that "consumers must reference `@v0`" no longer describes the shipped template, which references `@main` so that a node cannot validate against a standard older than the one it claims to follow. `@v0` still exists and still moves on release. Whether a federated node should ever pin is deliberately left open; if it settles, it warrants an ADR superseding this consequence and ADR 0006 §3.
template/.github/workflows/generate-index.yml:27
- This concurrency setting only serializes workflow runs; it does not make the checkout and the later
git pushatomic. Ifmainadvances after this checkout—especially via a change outside thepathsfilter, which queues no follow-up run—the composite action still pushes from a stale base, gets a non-fast-forward error, and leavesPROTOCOLS.yamlstale. Add a fetch/rebase-and-retry path around the commit/push (or otherwise reconcile against the latestmain) before relying on this as the race fix.
# Two merges in quick succession would otherwise run concurrently, and the second push would be
# rejected as non-fast-forward: its checkout predates the first run's index commit. Queue rather
# than cancel — a cancelled run leaves the index describing the previous commit.
concurrency:
group: generate-index
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
`ref: main` refreshed the checkout before generation but did not make the push race-free: a commit landing while the job installs or generates still makes it non-fast-forward. Worse, a docs-only commit does not match the workflow's `paths:` filter, so nothing re-runs and the index stays stale silently — the exact failure this whole change set exists to remove. The action now rebases onto the current tip and retries, up to three times. The index commit touches one generated file, so it rebases cleanly unless something else edited that file, and that case fails loudly rather than being forced. The checkout is pinned to `main` but the generator reads GITHUB_REF_NAME to build protocol_url values, so a workflow_dispatch from another branch or tag would index main's content under that ref's URLs. Pinned for the generator step too. Also: the README called `v0.x.y` tags immutable two paragraphs after saying a tag can be retargeted and only a SHA is genuinely immutable. They are versioned release tags, fixed by convention, and the sentence now says so. 77 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings remain.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
README.md:128
v0does not move on every release: the release workflow deliberately skips pre-releases, non-vX.Y.Ztags, and releases that are not highest on their line (as documented below). Please describe it as retargeted only for qualifying releases so the fixed-version guidance is accurate.
to reference if you have a reason to hold a fixed version — `v0` itself is not one, since it moves on
every release and is therefore no more fixed than `main`. Note that a release tag is only fixed by
actions/generate-index/action.yml:88
- When the caller checks out a SHA or another detached ref,
git rev-parse --abbrev-ref HEADreturns the literalHEAD. The first push then usesHEAD:HEAD, potentially creating/updating a branch namedHEADinstead of the caller's target; before this retry logic, the detached push failed rather than targeting the wrong ref. Require a symbolic branch (or accept an explicit target-branch input) before constructing this refspec.
branch="$(git rev-parse --abbrev-ref HEAD)"
for attempt in 1 2 3; do
if git push origin "HEAD:$branch"; then
template/.github/workflows/generate-index.yml:93
GITHUB_REF_NAMEis a reserved GitHub Actions default variable and cannot be overwritten by a step-levelenv. A manual dispatch from a branch or tag will therefore still make the generator see that ref, while checkout is pinned tomain, so the committedprotocol_urlvalues can point at a non-main ref and 404. Pass the target ref through a non-GITHUB_*input/environment variable and have the generator consume that explicitly.
GITHUB_REF_NAME: main
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
| - **Deciders:** Levi Waldron (User), AI Agent | ||
| - **Amends:** [ADR-0009](0009-name-metadata-fields-by-what-they-identify.md) — settles the tag question it raised and deferred | ||
|
|
||
| > **Amended by the template's move to `@main` (2026-09-14).** The consequence below that "consumers must reference `@v0`" no longer describes the shipped template, which references `@main` so that a node cannot validate against a standard older than the one it claims to follow. `@v0` still exists and still moves on release. Whether a federated node should ever pin is deliberately left open; if it settles, it warrants an ADR superseding this consequence and ADR 0006 §3. |
GITHUB_REF_NAME is runner-provided and a workflow cannot reliably override it through `env`, so the last commit's attempt to force `main` for the generator would not have worked. `detect_ref()` now checks PROTOCOL_INDEX_REF first — a name the runner does not own — and the action exposes it as a `ref` input. Two tests cover the precedence and that an empty value does not shadow the normal path. The retry loop I added last commit introduced a regression: `git rev-parse --abbrev-ref HEAD` returns the literal "HEAD" when detached, so `HEAD:$branch` would have created a branch called HEAD. Before the retry, a detached push simply failed. Now it fails deliberately, with a message saying why. ADR 0006 §3 still described consuming the actions pinned to a release tag, which contradicted the README and template. Bannered like 0011, and for the same reason: the pinning question is deliberately open, so this is an amendment rather than a supersession. The README said `v0` moves on every release. It does not — the retarget workflow skips pre-releases, non-vX.Y.Z tags, and releases that are not highest on their line, as the Releasing section below says. 79 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rebase-and-retry was two commits old and had produced two findings of its own: it pushed an index built from the pre-rebase tree, so a colliding commit that touched protocols/ would publish a stale index; and its HEAD:$branch refspec would have created a branch literally named HEAD on a detached checkout, which a plain push had simply refused to do. Making it correct means regenerating and amending inside the retry loop, which puts a second invocation of the generator into a shared action every future node runs. The race it defends against needs a merge inside a one-minute window in a repository where one person merges. So: a single push. A collision fails the run visibly and it is re-runnable from the Actions tab or by workflow_dispatch. A loud failure beats a silently stale index, which is the argument this whole change set rests on. The detached-HEAD guard goes with it — a plain push already refuses that case, which is what it did before the retry existed. 79 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The ADRs contain contradictory guidance that should be reconciled with the shipped @main configuration.
Review details
Suppressed comments (2)
docs/adr/0006-separate-standard-from-protocol-content.md:8
- The new amendment says the template no longer pins release tags, but §3 still states that content repositories consume these actions with YAML pinned to a release tag (lines 51–53). That leaves the ADR internally inconsistent and can mislead readers about the supported integration; please revise §3 or clearly label that paragraph as historical.
> **Amended by the template's move to `@main` (2026-09-14).** §3 below describes content repositories consuming the actions pinned to a release tag. The shipped template references `@main` instead, so that a node cannot validate against a standard older than the one it claims to follow. The argument for distributing the tooling rather than vendoring it is unaffected. Whether a federated node should ever pin is deliberately left open; see the matching note on ADR 0011.
docs/adr/0011-pre-release-tags-for-the-tooling.md:8
- This amendment leaves the ADR's own decision and consequences contradictory: lines 42–44 still tell consumers to use
@v0/a release tag, and lines 61–63 still say consumers must reference@v0, while the shipped template now uses@main. Please update those sections (or explicitly mark them historical) so the ADR does not direct new nodes to the superseded behavior.
> **Amended by the template's move to `@main` (2026-09-14).** The consequence below that "consumers must reference `@v0`" no longer describes the shipped template, which references `@main` so that a node cannot validate against a standard older than the one it claims to follow. `@v0` still exists and still moves on release. Whether a federated node should ever pin is deliberately left open; if it settles, it warrants an ADR superseding this consequence and ADR 0006 §3.
- Files reviewed: 8/8 changed files
- Comments generated: 0 new
- Review effort level: Lite
Each banner named one passage while the ADR carried several saying the same thing: 0011 also says in its Decision that a consumer who cannot accept the moving tag pins a release tag, and that a new repository starts on `@v0`; 0006's Consequences also call the content repository a second place where a version can be pinned. A banner that names one of those implies the rest still hold. Both now mark every consumer-facing passage as historical, and say what each ADR still decides — `v0.x` versioning and two separate axes for 0011, distributing the tooling rather than vendoring it for 0006. The bodies stay as written, per the convention these ADRs already use. 79 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@v0resolved to7f1beb7— a commit from before the Python rewrite, 22 behindmain. So both content-repo workflows were running the R tooling:The Pydantic validator merged in #39 was validating nothing. Every pull request in
agent-protocolswas checked by the old R validator, and CI was green throughout. That is the failure mode of a moving tag that nobody moves: silent, not loud.Why drop the tag rather than cut a release
The tag exists to decouple content repositories from a release cadence — ADR 0006 §3's argument. That assumes consumers outside your control. There is one content repository, the same person owns both, and nothing external consumes either, so the tag wasn't decoupling anyone. It was a pointer someone had to remember to move, whose failure mode is invisible.
But there's a better reason than expedience, and it's the one worth arguing:
Pinning is the ordinary answer for a library. It is a stranger answer for a standard. A consumer freezing a library dependency is behaving sensibly. A node validating against a snapshot of a standard is conforming to a version nobody publishes any more — and reporting success while it does. "Conformance to last year's spec" is not obviously conformance.
The README states this as a live question rather than a settled one, and asks a node that genuinely needs to pin to say why, so the answer comes from what federated nodes actually need rather than from a default chosen before any existed. If it settles, it warrants an ADR amending 0006 §3.
Changes
template/.github/workflows/{generate-index,validate}.yml→@main, with the reasoning in a comment where someone editing the file will see itREADME.md— the pinning paragraph rewritten; a node with a reason to pin is told to pin a commit SHA, since a tag can itself be retargetedWhat this gives up
A breaking change to
mainbreaks adopters' CI immediately, with no buffer. With one repository, a 77-test suite gatingmain, and a spec that is0.y.zand says anything may change — that's the right trade. It's also the conclusion ADR 0011 reached about@v1: a loud break beats silent staleness.Companion: waldronlab/agent-protocols#41, which this unblocks — its
committer-name/committer-emailinputs don't exist at@v0, and the R generator would have partially reverted #40 by re-flatteningdatabase_urls.🤖 Generated with Claude Code
Considered and declined
Pinning
actions/create-github-app-token@v1to a commit SHA. It is a mutable ref on an external action that receives the App private key, which is a long-lived credential — a real distinction from the refs below. But it is GitHub's own action, and the same argument would have us pinactions/checkoutandactions/setup-pythontoo. Left on the tag deliberately.Pinning
generate-index@main/validate-protocols@mainto a commit SHA. This is the decision in #42, not an oversight: a node validating against a snapshot conforms to a version of the standard nobody publishes any more, and does so silently. The trust boundary here is internal — both repositories have the same owner.Protecting tags on
agent-protocol-standard. The escalation path needs write access to that repository, which is the same set of people who already have write access to this one.Automatic reconciliation when the generator changes upstream. Tracking
@mainmeans the next run uses the new generator, but a generator change raises no event in a content repository, so the index can sit stale until an unrelated local change.workflow_dispatchis added as a manual escape hatch; aschedule:or arepository_dispatchfrom the standard repo would close it properly. Deferred until it is an actual problem rather than a theoretical one — there is one content repository and one person merging.Rebase-and-retry on a non-fast-forward push. Tried, then removed. To be correct it must regenerate the index after each rebase — the intervening commit may have touched
protocols/— which puts a second generator invocation into a shared action every federated node runs. The race needs a merge inside a one-minute generation window in a repository where one person merges. A single push fails loudly and is re-runnable from the Actions tab or byworkflow_dispatch; a loud failure beats a silently stale index, which is the argument this whole change set rests on.