feat: add reusable workflow that keeps AGENTS.md accurate in the PR that changed it - #311
feat: add reusable workflow that keeps AGENTS.md accurate in the PR that changed it#311FilipMasar wants to merge 9 commits into
Conversation
Keeps CLAUDE.md up to date inside the PR that made it wrong, instead of opening a separate PR after merge that nobody merges. Refs apify/integrations-team#98 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AGENTS.md now holds the doc and CLAUDE.md is a one-line `@AGENTS.md` pointer at it. This is the right way round: claude-code-action deletes and re-checks-out its SENSITIVE_PATHS from the base branch before the agent starts, and CLAUDE.md is on that list while AGENTS.md is not. So the agent now reads the branch's own version of the file it is judging. The gate hard-fails a repo that isn't laid out this way, printing the fix, instead of skipping with a warning. A skip is indistinguishable from "found nothing to fix", which is how a repo silently stops being maintained. Two things the old layout forced, both now gone: - The base-branch blob comparison in prep. With AGENTS.md never reset, `git status` answers "did the agent change anything" directly. - The per-commit API loop that bailed out when a human had edited the doc in the PR. It existed because the reset hid their edit from the agent, so committing reverted their work. The agent now sees it and edits on top, which is correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gate no longer lists the PR's changed files. Nothing judged that list any more, and the agent's own `gh pr diff` is already in the log, so it was an API call per run for a duplicate. The gate now needs no token and touches no network — every decision it makes reads the checkout on disk. fetch-depth drops to 1. Nothing reads a parent commit: the gate checks the tip's author and the only comparison left is the working tree against HEAD. The post-agent guard also refuses an empty AGENTS.md, not just a missing one. An Edit/Write agent cannot unlink a file but can truncate one, and that would have been committed as a "docs: update" that wipes the repo's doc. Comments that described the previous version of this unmerged file are rewritten as what a future reader needs: what to add back, and when. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A present-but-empty AGENTS.md passed the layout check. The agent is forbidden to create the file, so it would have had no claims to check and would report "nothing to do" on a repo with no doc at all — silence that means the opposite of what it looks like. Same -s predicate as the post-agent guard already uses. Found by lab/test-layout-gate.sh in apify/claude-md-lab, which covers 17 layout shapes locally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The layout error told the reader to run `printf '@AGENTS.md\n' > CLAUDE.md`. Where CLAUDE.md is a symlink to AGENTS.md the redirect follows the link and truncates the doc to that single line -- so the gate would fail the run, hand back an instruction, and the instruction would destroy the file it was meant to move. apify-core and apify/activepieces are both in exactly that shape, and apify-hubspot-integration and apify-integrations-backend are in the inverse one, with AGENTS.md as the link. The message now states the required end state, gives the command for the common case, and names the two shapes where a shortcut loses work: a symlink must be removed rather than written through, and two real files have diverged and need a human merge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-md-pr.yml -> agents-md-maintenance.yml. The doc has lived in AGENTS.md since the reset problem in claude-code-action made CLAUDE.md unusable as the source of truth, so the old name pointed at the pointer file rather than the document. Free to do now and not later: the workflow is not on main yet, so nothing pins claude-md-pr.yml@main and the rename breaks no callers. The comment claiming a rename would break every stub was written for the state after rollout; it now says so explicitly, and warns off renaming once stubs exist. claude-md-maintenance.yml keeps its name. It is the superseded post-merge workflow that already exists in the target repos, and onboarding deletes it by that exact filename. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
135 comment lines out of 358 was more than this needed. Cuts the verbose ones
and drops two that said nothing the code or the error messages next to them did
not already say:
- the gate's opening restatement of the required layout, which the two
::error:: echoes immediately below it state twice more
- "the agent's reasoning goes to the run summary either way", which the step's
own always() comment three lines up already explains
Everything load-bearing stays, because each of these was written down after
something went wrong: `'' == false` comparing equal on a payload-less event, %an
rather than %cn on API commits, check-ignore consulting the index, tr -d '\r' on
a CRLF checkout, concurrency on the job rather than the workflow, and why the doc
must live in AGENTS.md and not on claude-code-action's SENSITIVE_PATHS. The two
"deliberately nothing here" blocks stay for the same reason: they document code
that is absent on purpose, which is the only kind of decision a comment can
carry at all.
Comment-only. Verified by stripping comment lines from both versions and
diffing: every remaining line is byte-identical, and the agent prompt -- whose
markdown ## headings a naive comment filter would have eaten -- is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
drobnikj
left a comment
There was a problem hiding this comment.
The ticket https://github.com/apify/integrations-team/issues/98 described new flow differently with "human in loop step", where user approve by button or whatever that wants to update the files. Why you decide to do it this way?
Left some comments.
| echo "Committed to this branch, so it merges with your code. Review it like any other commit —" | ||
| echo "if it's wrong, edit or drop it." | ||
| } > /tmp/body.md | ||
| gh pr comment "$PR" --body-file /tmp/body.md |
There was a problem hiding this comment.
Suggestion: this posts a fresh comment per bot commit, so a PR that keeps changing shape accumulates one per push. Editing a single sticky comment instead would keep the thread readable.
| bloated `AGENTS.md` is a deliberate, reviewable cleanup of its own; smuggling it into an | ||
| unrelated PR is how a one-line rename turns into a 186-line deletion nobody asked for. | ||
| - Never document something you have not read. | ||
| - **Never create `AGENTS.md`.** It is guaranteed to exist — the workflow fails the run before |
There was a problem hiding this comment.
Note: should it to create the file if does not exist?
There was a problem hiding this comment.
I was thinking about it but it creates many cases and handeling them would blow up this workflow unnecessarily I think. So I would just keep it simple and say that the correct layout is prerequisite (it is one of the gate in gate step)
We need to update the PR reviewer he is currently too nice 😄 |
JanHranicky
left a comment
There was a problem hiding this comment.
The prompt looks good to me. Agree with Kuba comments, would preffer a human in the loop, that would approve the changes somehow.
Some PRs tend to change with a lot of pushes fixing review stuff etc. This action would trigger on every push right?
Would it make sense to have the action post a single comment and another action would watch the responds to that comment? This way a human could apply/decline the changes or re-request it the action run.
Yes, I deviated from the issue a bit and forgot to mention it in the PR description 😬
I would just keep it and see how it works for us in real PRs. Then change anything that is bothering us. What do you think? @drobnikj @JanHranicky |
Fixes apify/integrations-team#98
What
A new reusable workflow that keeps
AGENTS.mdaccurate.When a PR makes
AGENTS.mdwrong, a bot commits the fix to that PR's own branch and comments what it changed.Why
We already have
claude-md-maintenance.yml, which runs after merge and opens its own PR. Nobody merges those PRs, so the doc goes stale anyway. Fixing the doc in the PR that broke it means the change lands together with the code.How it works
AGENTS.md. It edits the file only if the PR made something in it wrong — a renamed script, a removed dependency, a new top-level directory.The agent only reads and writes files. Committing and commenting are ordinary steps after it finishes, so when something breaks it's clear which part did it.
How to use it
Nothing happens until a repo opts in — this is
workflow_callonly. Onboarding is two steps.1. The layout. Two regular files in the repo root, neither a symlink.
AGENTS.md— the whole docCLAUDE.md— first line is a pointer toAGENTS.md@AGENTS.md2. The workflow file.
For our teams integration repos, both steps are in apify/integrations-team#113
Tested various scenarios in a throwaway repo https://github.com/apify/claude-md-lab