The deploy step refuses a documentation-only merge, and the ship tick stalls 45 minutes on it #219
Labels
No labels
agent
agent:ci
agent:done
agent:failed
agent:needs-input
agent:refined
agent:refining
agent:running
agent:shipped
agent:skip
autonomous
autopilot
driven
local
plan
proposal
qa
qa-gap
research
retro
ship
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jeroen/cartopolis#219
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
.forgejo/workflows/ci.yml:64-74ignoresdocs/**and**/*.mdon both thepush: [main]andpull_requesttriggers, so a documentation-only merge commit onmaingets no Actions run at all. Two consumers of that fact disagree.tools/autopilot.tsknows the rule. WhenjobsFor(sha)comes back empty,shipReadyre-reads the branch diff and merges without a verdict if every changed file is docs (tools/autopilot.ts:279-292, the predicate itself at:287). A third prose copy of the same rule sits inmainVerdict's doc comment attools/autopilot.ts:421-424.tools/deploy-maindoes not.shipReadyhandsmainto it unconditionally attools/autopilot.ts:309→deployMainat:389, which runs the script bare (:392), sodeploy-mainresolves the sha as the head ofmainitself (tools/deploy-main:121-122) and enters the wait loop attools/deploy-main:135-152. That loop only leaves when at least one job exists and none is pending (:141); with no job ever, it spins at 20-second intervals untiltimeout_min(default 40,tools/deploy-main:44) and exits 1 withrefusing: no Actions job ran(:144).Both halves are individually right.
deploy-main's gate is the onedocs/direction.md:115-119calls load-bearing — "a silent runner failure publishes untested code" — and the issue is explicit that it must not be weakened. What is wrong is that the lane spends the whole timeout discovering something it already computed, and then posts the failure text attools/autopilot.ts:411("Merged, but not deployed … the public services are still on the previous build") for a commit that had nothing to publish.The merge commits named in the issue are docs-only in the API's own view:
67c0c14reports one changed file,docs/notes/road-junction-geometry.md(GET /api/v1/repos/jeroen/cartopolis/git/commits/67c0c14,files[]).Approach
Take option 2 from the issue — the autopilot does not call the deploy — and factor the rule so it is read from
ci.ymlinstead of hardcoded. One file,tools/autopilot.ts, plus documentation.Recommending option 2 over option 1 for one reason worth stating:
deploy-mainis the gate whose only job is to refuse, and teaching it a new way to succeed — a YAML parse plus glob matching, in bash/curl/python with no YAML library — puts new code in the most dangerous place in this lane.shipReadyhas already made the more consequential decision (merge with no verdict) from the same fact; adding "and therefore there is nothing to deploy" to a decision already taken adds no new authority.deploy-mainis not touched, so its refusal behaviour is bit-identical.ci.ymlonmain. Add a helper besidefrontier()(which already fetches a file frommainover the API attools/autopilot.ts:501and is the pattern to copy) that fetches.forgejo/workflows/ci.yml?ref=main, extracts thepaths-ignorepatterns, and answers "is every one of these files ignored by CI?".paths-ignore:key and take the- '…'items indented under it. Require at least one block and require all blocks to have identical contents; if they differ (someone diverged thepushfilter from thepull_requestone), that is not a rule this helper can state.<prefix>/**→ the file starts with<prefix>/;**/*.<ext>→ the file ends with.<ext>. Any other pattern shape is unrecognised.docs/direction.md:115exists to close.tools/autopilot.ts:287, and carry its answer forward: hold the result in a local alongsidemerged(:250), and at:309either calldeployMain(n)as now, or — when the merge was docs-only — post one comment saying documentation only, nothing to deploy, and skip the call. The label handling at:305-306and the merge comment at:307are unchanged. Memoise theci.ymlfetch for the duration of ashipReadypass; it loops over up to 20 issues.tools/*.ts(nopackage.json, notsconfig.json, no CI job runs bun overtools/), andstatusdoes not exerciseshipReady(tools/autopilot.ts:1087). Add a read-only subcommand —bun tools/autopilot.ts paths <branch-or-sha>— that prints the patterns it parsed out ofci.yml, the changed files it resolved, and the verdict, and exits. It writes nothing and posts nothing. This is the only way an implementer can demonstrate the rule is right before it decides a real deploy.docs/workflow.md:137says the lane runstools/deploy-mainafter a merge; amend it to say that a documentation-only merge skips it. Record the standing fact indocs/notes/ci-build-cost.md(which already ownspaths-ignoreat:86): the filter now has readers outside the workflow file, so a pattern added there is a patternautopilot.tsmust be able to translate, and one it cannot translate stops the lane rather than deploying blind. Fix the stale prose copy of the rule in themainVerdictcomment attools/autopilot.ts:421-424to point at the helper rather than restatingdocs/**and*.md.Note that
tools/autopilot.tsis inside the autopilot's own fence (FENCED,tools/autopilot.ts:212) and.forgejo/workflows/is too (:211), so this branch keeps its branch and losesshipby design. It lands for review. Do not apply theshiplabel.Acceptance criteria
docs/and.mdno longer appear as a path rule anywhere intools/autopilot.ts— the only source of the patterns is.forgejo/workflows/ci.ymlonmain, fetched at run time.tools/deploy-mainis unmodified.git diff main -- tools/deploy-mainis empty.shipReady's existing docs-only merge path (tools/autopilot.ts:279-292) behaves as before for a branch whose files are all underdocs/or end in.md: it merges without a verdict.deployMainis not called, and exactly one comment is posted in its place. It says the change is documentation only and there is nothing to deploy, and it does not claim a failure or that "the public services are still on the previous build".deployMain(n)is called exactly as it is today, with no new arguments and no change totools/autopilot.ts:389-415.pathssubcommand, one run per case):ci.ymlcannot be fetched; the file has nopaths-ignoreblock; the two blocks in it differ; a pattern is present that is neither<prefix>/**nor**/*.<ext>; the changed-file list is empty. Each logs which of those it was.bun tools/autopilot.ts paths 67c0c14reports the two patterns fromci.yml, the one changed filedocs/notes/road-junction-geometry.md, and a verdict of "ignored by CI". It posts no comment and changes no label.bun tools/autopilot.ts paths ab430c5(one changed file,tools/autopilot.ts) reports "not ignored".bun tools/autopilot.ts statusstill runs and prints the same shape of JSON.docs/workflow.md:137's paragraph states the docs-only skip.docs/notes/ci-build-cost.mdrecords thatpaths-ignorehas a reader outside the workflow file, and that an unrecognised pattern stops the lane rather than deploying blind.mainVerdictcomment attools/autopilot.ts:421-424no longer restates the rule in prose.agent:doneand withoutship.Verification
Read-only, no Rust involved; nothing here needs a GPU, a shot or a workstation.
Not checkable here, and it is what the issue says success is: the next documentation-only ticket the lane ships posts no failure comment and does not stall. That can only be seen in the lane's own comments after the change is copied into the running container.
The deployed copy is a manual step.
docs/workflow.md:854records that/data/agent/autopilot/autopilot.tsis a copy oftools/autopilot.ts; the mount is read-only and the container picks up a new file on its next tick. Merging this branch changes nothing until that copy is made, so the fix does not take effect on merge alone. Say so when handing the branch over.Out of scope
tools/deploy-mainat all, including its message. A human running it by hand right after a documentation-only merge will still wait--timeout-minand getrefusing: no Actions job ran. That residual is deliberate: it is the untouched gate, it costs a person aCtrl-Crather than costing the lane 45 minutes six times a day, and closing it means putting the path rule inside the refusal gate, which the issue anddocs/direction.md:115-119both warn against.timeout_min, or any other change to how long the deploy waits.deploy-mainaccept a--sha, or having the autopilot pass one.FENCEDtable, theshiplabel, or the lane's other gates.package.jsonortsconfig.jsonfortools/..forgejo/workflows/ci.ymlfilter itself — no pattern is added, removed or reworded.Open questions
None.
Branch:
fix/219-autopilot-docs-only-deployOriginal request
Found by the 3-day retrospective on #218, reading the lane's own comments for 2026-08-29 and 2026-08-30.
What happens
Six tickets in the period ended with this comment, minutes-to-an-hour after a clean merge:
#186, #192, #176, #197, #199 and #201. Every one of them is a documentation-only change — a
docs/notes/file and a row indocs/direction.mdrecording that a source was checked and not taken. There was nothing to deploy and nothing to verify.Why it happens
.forgejo/workflows/ci.ymlhas apaths-ignorerule, and states its reason at length: a Markdown change should not compile Bevy. So a docs-only merge commit onmaincorrectly gets no Actions job at all.tools/autopilot.tsalready knows this.shipReadycompares the branch againstmain, sees that every changed file is underdocs/or ends in.md, logs "documentation only — CI does not run on these, shipping" and merges without a verdict. That is right.tools/deploy-maindoes not know it. Its gate (around line 130) loops until a job appears, and when none ever does it exits 1 withrefusing: no Actions job ran. That is also right in isolation — it is the load-bearing second checkdocs/direction.mddescribes, the one that stops a timed-out CI wait from reaching the deploy, and it must not be weakened. The two halves simply disagree about what a docs-only commit is.What it costs
Two things, and the second is the one that matters.
deployMainis awaited insideshipReady's loop, and the wait runs totimeout_minbefore refusing. #186 merged at 20:51 and the refusal landed at 21:35; #176 merged at 21:52 and refused at 22:36. That is ~45 minutes of the lane sitting still, per docs-only ticket, six times in this period.Where the seam is
tools/deploy-main— the wait loop andrefusing: no Actions job rantools/autopilot.ts—shipReady's docs-only branch (the rule already exists there), anddeployMain.forgejo/workflows/ci.yml— thepaths-ignoreblock the rule has to agree withWhat would settle it
Either half works; whichever is chosen, the two must read the same rule from one place rather than each carrying its own copy of "docs/ or *.md", because the next path added to
paths-ignorewill otherwise re-open this.deploy-mainlearns the rule. Compare the commit's changed files againstpaths-ignore; if every one is ignored, say "nothing to deploy: documentation only" and exit 0. It must stay a whitelist of ignored paths, never "no job found means fine" — that is exactly the hole the gate exists to close.shipReadyhas already computed the file list by the time it merges; when the merge was docs-only, skipdeployMainand comment "documentation only — nothing to deploy".Success is judged from the lane's own comments, not from a picture: the next docs-only ticket merges and posts no failure, and the ship tick does not stall for the timeout.
A note on labels
Not
ship.tools/autopilot.tsandtools/deploy-mainare both inside the autopilot's own fence (autopilot.ts, theFENCEDtable), so a branch touching them keeps its branch and losesshipby design. It lands for review, which is the point.Filed by the retrospective on #218.
🤖 Refined by the viberfox issue agent. Reply with @agent refine and what is wrong to have this rewritten.
agent:donenever means "CI agreed" #220🤖 Refining this before anything gets built — reading the code, no branch, no commits. If it turns out I'd have to guess at the requirements I'll stop and ask instead.
🤖 Refined. The description above is now the spec; I'll pick it up for implementation on the next poll.
If the direction is wrong, say so now — @agent refine with a correction rewrites it, and
agent:skipstops me.🤖 viberfox issue agent on the VPS — 19 turns · $2.01 · session
68bbdae2-efde-46ae-8075-137f3c575b0d. Reply with @agent to continue this session.🤖 Picked this up — working on branch
fix/219-autopilot-docs-only-deployin a fresh worktree on the VPS. I'll report back here when I'm done.Label an issue
agent:skipto keep me off it, ordrivento keep it in a session you are driving.The autopilot no longer calls the deploy script after a merge that CI does not build, and the rule for "does CI build this?" is now read from the workflow file instead of being written down a second time in the autopilot.
What changed — three files, no Rust.
tools/autopilot.ts: a new helper fetches.forgejo/workflows/ci.ymlfrommainat run time and parses thepaths-ignoreitems out of it. It replaces the hand-writtendocs//.mdtest that was inshipReady. When a merge was one of those,deployMainis not called; one comment is posted saying the change is documentation only and there is nothing to deploy. Any other merge deploys exactly as before —deployMainitself is unchanged.docs/workflow.md: the paragraph about the deploy step now states the skip.docs/notes/ci-build-cost.md: a new section recording that thepaths-ignoreblock now has a reader outside the workflow, so a pattern added there must be one the autopilot can translate, and one it cannot stops the lane rather than deploying blind.tools/deploy-mainis untouched.git diff origin/main -- tools/deploy-mainis empty. Its refusal behaviour is bit-identical; it is simply not called for these commits.Fails closed in six ways. Workflow unfetchable, no
paths-ignoreblock, an empty one, the two copies disagreeing, a pattern shape it does not recognise, or an empty changed-file list — each answers "not ignorable", logs which one it was, and the branch goes back to waiting for CI.How I verified it. There is no test runner for
tools/, so I added a read-only subcommand and ran it.bun build --target=bun tools/autopilot.tsparses. Against real commits:paths 67c0c14→ patternsdocs/**,**/*.md; one filedocs/notes/road-junction-geometry.md; verdict ignored by CIpaths ab430c5→ one filetools/autopilot.ts; verdict not ignoredpaths 20b036a→ two files, one of themtools/autopilot.ts; verdict not ignoredAll five fail-closed cases were run one at a time against doctored workflow files via
CARTO_AUTOPILOT_CI_WORKFLOW, and each printed its own reason.statusstill prints the same JSON.What is not verified. The end-to-end path — a real documentation ticket merging and posting the new comment instead of stalling — cannot be exercised here; it needs a live ticket with a
shiplabel. The success test the issue names is the lane's own comments after the next documentation-only ticket.One thing to do by hand. The running container reads
/data/agent/autopilot/autopilot.ts, a copy of this file. Merging this branch changes nothing until that copy is made; the container picks it up on its next tick.The branch is
fix/219-autopilot-docs-only-deploy, pushed. It touchestools/autopilot.ts, which is inside the autopilot's own fence, so it keeps its branch and does not get theshiplabel — it lands for your review.Branch
fix/219-autopilot-docs-only-deploy(pushed)🤖 viberfox issue agent on the VPS — 35 turns · $3.43 · session
3ac571e7-9dc9-4348-8cf1-7bae9c715b3e. Reply with @agent to continue this session.🤖 Marked
agent:donewithout a CI verdict ona9599f3c— no workflow reported on this commit. The branch is waiting for review; check the run yourself before landing it.Stuck, waiting for you: nonewhile three tickets wait for a person #270