Changelog
Generated from Conventional Commits at each release. Older majors are archived under docs/changelog/.
3.145.1 (2026-09-15)
Bug Fixes
golden-master: the seal declines per SET, never per directory (#1149) (6ceb323)
why
seal_holdout relocates the held-out set so the hardening loop cannot read it -- the seal was a sentence in a skill until it was made mechanical. A COMMITTED set is deliberately exempt: it awaits its own gate, and moving tracked files would leave uncommitted deletions a finalize refuses to merge while burning that set's single scoring on a gate that does not own it.
3.145.0 (2026-09-15)
Features
dsl: a bot in several files — import "lib/x.bot" read as one unit, one identity, saved by provenance (#1225) (309d6c1), references #1010 #1164 #1010
why
The first piece of the multi-file unit (ADR-098 §3, lot 3 of #1010):
importis a keyword likedsl, read at the head of a file — after the header and the leading comments, before any declaration (E044 otherwise) — with a quoted, relative, slash-separated.botpath (E045 for the rest: absolute, a drive,..,\, NUL, junk after it); the same path twice is one import. The AST carries the imports as written, the JSON document as their paths, the writer puts them back where the parser reads…
3.144.1 (2026-09-15)
Bug Fixes
server: the merge-gate net must outlive the outage it exists for (#1224) (1f30ed3)
why
A dead gating run was reachable by the reconciler's net for 60 minutes. The outage class that net exists for is a provider usage window, and a weekly one shuts for DAYS — so the net closed while the thing it guards against was still happening.
3.144.0 (2026-09-14)
Features
remote: a CLI launch can aim a run at a repository (#1161) (0c95277)
why
POST /api/runshas acceptedrepo_url/repo_ref/connection_idall along — the cloud runner clones the repo into the workspace before sandboxing, and the inbound-webhook path sets all three.iterion remote runs launchexposed none of them, so the only way to launch a repo-scoped bot with a checkout was a webhook or the studio.
3.143.0 (2026-09-13)
Features
ship the iterion assistant epic (#480) (6eea0ce), closes #482, references #14 #333 #334 #333 #333 #476 #476 #493 #481 #485 #486 #489 #485 #487 #488 #490 #491 #493 #566 #1135
why
The three dock presentations (closed / floating / docked-right), the lg breakpoint rule and the persistence helpers were private to FloatingChatPanel, so nothing outside /runs/:id could reuse them. They now live in @/lib/chatDock/dockState with openedDock() taking an injectable viewport width, which makes the breakpoint rule testable without a DOM. FloatingChatPanel keeps a ChatDock type alias so the run console's existing imports keep resolving.
3.142.6 (2026-09-13)
Bug Fixes
3.142.5 (2026-09-13)
Bug Fixes
3.142.4 (2026-09-13)
Bug Fixes
3.142.3 (2026-09-13)
Bug Fixes
3.142.2 (2026-09-13)
Bug Fixes
3.142.1 (2026-09-13)
Bug Fixes
3.142.0 (2026-09-13)
Features
bots: concise Revi reviews with linked run details (#1173) (7f695b3), references #1172 #1172
why
Keep clean reviews to one visible sentence and retain actionable details in inline comments or explicit fallbacks when anchors are unavailable. Move review scope and method into the existing collapsed run details, and ask only questions that require a material maintainer decision.
3.141.0 (2026-09-13)
Features
bots: show Revi AI run details in a collapsed review footer (#1167) (6a25a6a), references #1166
why
Show served models and harnesses, requested effort, and engine token counters without changing findings or gate semantics. Refs #1166.
3.140.3 (2026-09-12)
Bug Fixes
review-pr: the merge step no longer pins a gpt review to the Claude wire (#1160) (240d066), references #1150
why
mono_familypicks which reviewer JUDGES; it never moved who MERGES.agent convergesits on every path and carried a hardcodedbackend: "claude_code", so selecting the gpt family bought a review that completed and then died on the Anthropic weekly cap at the aggregation step. A family switch that still requires the other family is the half-wired shape the parity doctrine calls a defect — measured today, when the deployment's seven-day window crossed its hard cap and every claude_code run…
3.140.2 (2026-09-12)
Bug Fixes
connector: the guarded client is carried by the VALUE, and a cleartext origin is named (#1150) (302b949)
why
The executor accepted any non-nil client. A default one and the guarded one have the same type, so nothing in the path could tell them apart, and the whole SSRF posture rested on one production site remembering to pass the right one — a convention held today only by there being exactly one such site, and due to break at the second (the cloud tier, which builds its own client).
3.140.1 (2026-09-11)
Bug Fixes
golden-master: a spent held-out set is refused before the boot, not after the replay (#1117) (51e8170)
why
The held-out REUSE check needs nothing the application provides: spent_fingerprints reads committed audit directories, mutant_fingerprint hashes a mutant directory, and held_meta is in hand a hundred lines earlier. It was nonetheless the last statement of the gate -- after app_up and the entire corpus replay, inside the try whose finally tears the application down.
3.140.0 (2026-09-11)
Features
botscaffold: a gallery of eight canonical shapes behind
bots create --templateand the studio builder, each held to its form (lot 1 of #1010) (#1114) (bb6b02b), closes #1110, references #1110 #1110why
The five templates of the bot-creation gallery all rendered the same graph — one campaign agent,
campaign -> done, from the one main.bot.tmpl — and differed by prompt and metadata only; the forms the catalog bots are made of (a campaign under a deterministic gate with a bounded loop and a typed fail, a reviewer fan-out with a compute convergence, a plan under a human gate, a verified action, …) existed nowhere at a size an author could read whole, and a Verified Action had no .bot instance in…
3.139.0 (2026-09-11)
Features
sec-audit: the capped findings can travel in the envelope, so triage is not pinned to one backend (#1141) (5551f72)
why
triage reads the scanner findings through json_paths — it opens the files the tool nodes wrote. That works only where triage shares a filesystem with those nodes, and exactly one backend does: the sandbox-routed claude_code.
clawis in-process and never enters the sandbox (delegate.go: "In-process backends (claw) refuse to start when this is set"), andcodexrefuses to run inside one at all (codex.go: "cannot run inside Iterion %s sandbox with the pinned SDK").
3.138.2 (2026-09-11)
Bug Fixes
sec-audit: the deep scanner's retry resumes instead of starting over (#1139) (97db319)
why
deepsec exits 1 as soon as ONE batch errored, so the node's single retry is reached far more often than "the pass crashed" suggests — and it opened a FRESH run every time. Every batch the first attempt had already investigated was re-investigated and paid for again, under the same bound that had just expired, which on a large repository is the difference between finishing and timing out twice.
3.138.1 (2026-09-11)
Bug Fixes
dsl: an escaped quote in post_create reaches the shell as a literal quote (#1086) (9cb9ea2)
why
A
"..."DSL string is lexed in legacy escape mode unless the file opts into## strict-escape: on— and no bot in the catalogue does. Legacy mode keeps every \X VERBATIM, so a backslash-escaped quote survives into the shell, which reads " as a literal quote CHARACTER. The command then runs with quotes inside its arguments instead of around them.
3.138.0 (2026-09-11)
Features
connector: the connector catalog — deterministic nodes over generated packages (P0) (#1119) (603d2a1), references #1072 #1073 #1072 #1092 #1092 #1067
why
A connector's operations are data — a method, a path, flat typed params, a typed result, closed error classes — generated from the vendor's own API description and refined by an authored overlay. One model serves both offers: the deterministic
tool … action:path and the MCP facade differ in who chooses the arguments, never in what the call is.
3.137.0 (2026-09-11)
Features
sec-audit: the deep scanner's findings leave the pod (#1104) (e68d353)
why
The deep scanner writes its findings to a file inside the sandbox, and the pod is destroyed with the run. The published envelope carries the PATH, so a pass that dies AFTER it — at triage, at the jury, on a provider usage cap — takes the whole contribution with it.
3.136.3 (2026-09-11)
Bug Fixes
sec-audit: the deep scanner reports what it did, and its timeout escalates (#1100) (8e82c61)
why
Two defects, both measured on real runs, both of the same family: a failure that leaves no readable trace.
3.136.2 (2026-09-11)
Bug Fixes
runtime: a convergence its whole fan-out failed lost every incoming mapping (#1120) (4745fd9), references #559 #1113 #484 #484 #559 #1113 #1118 #484
why
When a fan-out stabilizes without a single branch producing output — every branch failed under
best_effort(#559), or afan_out_eachfanned over an empty collection (#1113, the twin site) — the convergence node ran with NO incomingwithmapping at all. Not just the ones reading the dead branches: also the ones reading a durable parent output or a var, which the failure never touched. Atoolnode was then handed the literal{{input.x}}in its command, since shell rendering deliberately…
3.136.1 (2026-09-10)
Bug Fixes
server: the origin gate refused in silence, so its own safety was unobservable (#1108) (5c0f3f8)
why
Widening the CSRF boundary from 70 hand-picked handlers to every state-changing /api route left one question open: is it refusing anything it should not? The gate answered the caller with a 403 and recorded nothing, so "no legitimate client is being refused" and "we have no way to see one" produced identical evidence — an empty grep. That is how the board-MCP transport stayed an inference: sandboxed claude_code and pi POST to /api/v1/mcp/board, which the gate covers, and the claim that their…
3.136.0 (2026-09-10)
Features
dsl: the registry's value lists are held to the compiler, a block's remedy to its host, and worktree: is checked (C142) (#1103) (142755f), references #1092 #1084 #1094 #1010 #1010
why
Revi's second verdict on #1092 named the class behind two guards that lot 1a added: the registry's value lists were proven to the PARSER only, and the "outdent it to the <host>'s level" remedy was right only because every multi-host block happened to call enterBlock.
3.135.1 (2026-09-10)
Bug Fixes
runs: a credential with no fingerprint still names the tier that paid (#1109) (290b798), references #1105 #1105
why
#1105 collected the tiers inside the FINGERPRINT harvest, which is keyed on an audit identity and skips a credential that has none — setOAuthFingerprint refuses an empty stamp outright, so an unstamped forfait never even enters the map that walk reads. A run funded only by one reported no tier at all: an empty answer where the GRANTED log line says
<unstamped>, which is the confident silence the field exists to remove, on exactly the odd credential an operator is most likely to be chasing.
3.135.0 (2026-09-10)
Features
runs: a run says which tier paid for it, and says it again after a resume (#1105) (75e4413), closes #991, references #992
why
run.bot_source_tieranswers "which bundle served this launch". Nothing answered the question an operator asks at least as often — "who paid for this run?" — although the publisher computes it: it resolves the credential through five tiers and names the winner in ONE INFO line. Answering for a run whose logs have rotated meant not answering at all.
3.134.0 (2026-09-10)
Features
- home: add a playful open source invitation above the footer (68e9c80)
- home: consolidate stack capabilities around Devbox (083b3e6)
- home: highlight Devbox and language toolchains (31aaf3d)
- home: showcase end-to-end bot missions (b3f8fa7)
- web: invite visitors to star Iterion on GitHub (3a40098)
Bug Fixes
docs: render DSL with-map forms as inline code (2ef083b)
why
VitePress interpreted the bare braces in the property table as HTML attributes, breaking the docs build. Format the syntax as inline code in the generator and introductory table, then regenerate the reference.
3.133.0 (2026-09-10)
Features
dsl: a property registry the parser is held to, and E012 names the remedy (lot 1a of #1010) (#1092) (57564e7), closes #1084, references #1084 #1094
why
Lot 1a of #1010 (#1084). The DSL's property surface was written by hand in five places (the parser's switch arms, the EBNF, the readable grammar, the root SKILL.md, the whats-next quickref) and drifted: the quickref's canonical examples did not parse until lot 0 rewrote them, and an unknown property was refused with E012 and a pointer to a table.
3.132.8 (2026-09-10)
Bug Fixes
credusage: a usage listing that hides its filter reads as a frozen meter (#1097) (32fa082), references #1087 #1087
why
The admin per-credential route answers for ONE tier and defaults to
platformwhen the caller names none — correct, since no tenant view can show that tier, and nothing in the response said so. Both routes also labelled every answer with amonththey derived fromtime.Now(), while reading no?month=at all: a caller asking for August was served September, byte for byte, under an August-shaped question.
3.132.7 (2026-09-10)
Bug Fixes
forge: a fixer rewriting a branch says so, instead of being discovered at push time (#1064) (40a0ba7)
why
A FIXER run holds no required check. markGateInFlight claims
gate_context, and a fixer has none — it answers a review rather than gating the merge — so for the tens of minutes it works, NOTHING on the pull request says it is there. The only signal that ever existed is a comment, and only in one case: a quota park, whose pause notice already tells the reader not to push. A fixer that is simply working is silent.
3.132.6 (2026-09-10)
Bug Fixes
runner: a per-credential spend never disappears without a line (#1090) (dda7468), closes #1052, references #1087
why
Four declines dropped an attempt's per-credential metering in silence: no counter wired, no credentials on the context, and — the one that matters — a resolved slot carrying no fingerprint. Its sibling one line above (no slot at all) already warned; this one just
continued.
3.132.5 (2026-09-10)
Bug Fixes
sec-audit: a failed scanner stops leaving an output file behind (#1079) (dd24c6d)
why
scan_health judges coverage from the filesystem: an output file that exists and parses counts as "that scanner ran". A tool that runs, FAILS, and still leaves a parseable artifact therefore reads as full coverage over a broken toolchain.
3.132.4 (2026-09-10)
Bug Fixes
auth: an org admin was offered teams the switch then refused (#1083) (8bb175b)
why
buildOrgTreelists every team of an org for its admins, synthesizing aRoleAdmingrant — deliberately, sincecanManageTeam/orgAdminOfTeamalready let them write to each of those teams.SwitchTeamnever learned the same rule: it had a step-in for super-admins only, so every other team came back403 user is not a member of the team. The studio builds its switcher from the first and calls the second, so the click did nothing at all, with no message. Measured on prod: 19 teams offered,…
3.132.3 (2026-09-10)
Bug Fixes
dsl: empty blocks have a written form, an include never resolves against a relative name, C141 on a use of an empty group (#1067) (140102a), references #1010 #1015
why
The block half of the empty-header class #1050 closed for declarations (Revi's R7f55fb on that PR):
budget:,memory:,compaction:,mcp:,auth:,cursors:,recovery:,sandbox:(and itsbuild:/network:) and the four top-level blocks were written as a bare header when empty — reachable from a plain file whose only property is zero-valued (max_cost_usd: 0,args: {}) and from the canvas document's{}— and a bare header did not parse (E002), so the save guard refused the…
3.132.2 (2026-09-10)
Bug Fixes
docs: the docs site has not built since the browser-security page landed (#1077) (a613c3e)
why
check-links.mjsresolves this site's github blob links against the real tree, andstudio/src/lib/monaco.tsis not a path in it — the module ismonaco.tsx. One character, andpnpm -C docs buildexits 1 on it, so every push to main since d01f80707 (08:54Z, six commits) has failed to publish the documentation.
3.132.1 (2026-09-10)
Bug Fixes
runtime: a bot's devbox.json reaches the driver bots actually run on (#1061) (505c54c)
why
devbox.jsonnext to amain.botis the documented, durable way for a bot to declare the binaries its steps need — and until now it was declined on the kubernetes driver, which is where bots actually run in cloud. The event said so (no host bind mount on this driver), but only to whoever went looking: nothing failed except, later, the step that needed the tool.
3.132.0 (2026-09-10)
Features
credentials: a tenant may hold a CHAIN of forfaits, not one per kind (#1065) (ed8c522), references #945
why
The store held exactly one OAuthRecord per (owner, kind), enforced by a unique index. That made the credential chain no deeper than the tiers themselves: an operator holding four Claude subscriptions could wire two — their org's and the deployment's — and had no way to say "try these in this order". On 2026-09-08 that ceiling stopped every claude_code run on a production deployment for three hours: the org forfait's five-hour window closed, the single tier behind it was already spent on its…
credusage: the repository becomes an accounting dimension (#1069) (9f0247f), references #950
why
"A quota per repo" had no subject to attach to: credusage.Key was {fingerprint, provider, tier, tenant} × month, and nothing carried the repository a run targeted into accounting — so "one busy repository is eating the shared subscription" was unanswerable while it happened.
3.131.5 (2026-09-10)
Bug Fixes
model: a captured turn records the backend that produced it (#1062) (e8ec6eb), closes #1053
why
delegateHooksFor already receives the node's RESOLVED backend and threw it away, stamping delegate.BackendClaudeCode on every captured turn. pi fires the same OnTurnFinished hook (pi_rpc.go), so every pi turn was persisted under another backend's name in store.TurnCheckpoint.Backend.
3.131.4 (2026-09-10)
Bug Fixes
security: close the same-site CSRF hole on the API, and the browser gaps beside it (#1058) (d01f807)
why
The Origin check was opt-in per handler, and opt-in drifted: 70 of 247 state-changing routes called requireSafeOrigin, leaving the BYOK keys, team and org secrets, OAuth forfaits, platform LLM credentials, forge connections, webhooks and org administration ungated.
3.131.3 (2026-09-10)
Bug Fixes
deps: close the five docs-chain advisories without shipping an alpha (#1060) (1aa36f5), closes #625
why
All five open Dependabot alerts live in ONE chain: vitepress 1.6.4 pinned vite 5.4.21, which pinned esbuild 0.21.5. Moving vite carries esbuild with it, so four of the five are one fix, and @babel/core is the fifth.
3.131.2 (2026-09-10)
Bug Fixes
dispatcher/native: a refused inotify watch no longer freezes the board index until restart (#1051) (7871eac), references #1047 #1020
why
Adversarial re-attack of the previous two commits (2 high, 2 medium, 2 low), each with a red-then-green test:
forge: a store that could not answer is no longer read as "you have no App" (#1059) (bd01eeb), closes #969, references #969
why
Resolving a connection's GitHub App returned one
ok bool, so "this tenant registered none" and "the store could not be read" were the same answer. Every caller then acted on the wrong one, and the two residuals #969 left behind turn out to be one user-visible defect.
3.131.1 (2026-09-10)
Bug Fixes
dsl,cloud: lot 0.5 transport — complete JSON codec, lossless unparser, includes travel with the AST (ADR-098) (#1050) (d57f911), closes #1012 #1010 #1015 #1013, references #1015 #1013 #1010 #1039 #1010 #1012 #1049 #1010 #1015 #1013 #1015
why
The AST JSON codec is what a cloud launch puts on the queue and what the studio saves through. It dropped four constructs:
group/use(a workflow using them compiled from its .bot and failed with C008/C001 after the round-trip), theas foreachclause on an edge (silently gone) and a named resource pool (slot: ["a", "b"]came back as a bare capacity). So a bot with any of them behaved differently on a cloud launch than locally. The codec now mirrors every declaration — groups through…
3.131.0 (2026-09-10)
Features
persist artifact restart contracts (#1020) (5b81de3), closes #1047, references #1039 #1051 #1047 #1051
why
Rewindvalidated every persisted artifact contract before it knew its pivot — i.e. beforedownstreamOfcomputed what the rewind invalidates — so an artifact the operation was about to supersede refused the whole operation. Under theenforcecontext policy that made the loop the command exists for unusable:--autotargets the node whose declaration just changed, which is precisely the node whose contract no longer matches, and the rewind is what clears it (its tombstone carries no…
3.130.0 (2026-09-09)
Features
review-pr: read the issues a PR links, with no per-repo configuration (#1017) (7d95dc6), closes #1014, references #123 #1003 #997
why
Ticket conformance shipped in 0.6.0 behind
tracker_api_base, a var an operator had to pin per repo. Nobody did: across the 16 connected GitHub repos it is set on ZERO of them, so a PR saying 'Fixes #123' was reviewed without anyone reading #123. The check existed and never ran.
3.129.2 (2026-09-09)
Bug Fixes
usage: an aggregated token count no longer claims to be input (#1052) (ba4f730), references #992
why
A CLI delegate reports ONE token count and no split. Every usage surface booked it under
input_tokens, which kept a sum correct and made the named field a lie: measured on ovh-prod, every credential filled exactly one of the two fields and zeroed the other, so each per-token ratio, cache-hit reading and input/output share taken from the public endpoint was wrong, with nothing on the row saying so.
3.129.1 (2026-09-09)
Bug Fixes
forge: a re-provision erased three fields it does not own (#1046) (22b660b)
why
Provision rebuilds RepoIntegration from the REQUEST and Updates it, and the update replaces the whole document. Any field the literal omits is therefore erased — silently, on a live repo, with a 200 in reply.
3.129.0 (2026-09-09)
Features
runner: let a read-only bot decline the workspace checkpoint (#1042) (a7db190)
why
The mid-run workspace checkpoint force-pushes the pod's tree as
iterion/run-<id>-checkpointon the run's OWN remote — the repository the bot was pointed at — and reads that tree withgit add -A, so a bot's scratch directory rides along: untracked, and nothing ignores it.
Bug Fixes
forge,server: a key iterion cannot read is not the forge being down (#1030) (daf30e7), references #969
why
The App-token mint signs its JWT from a stored private key BEFORE it opens a socket, so a key that is not parseable PEM fails inside what reads like a pure remote call. Three handlers default to 502 for anything the classifier does not recognise — a sound default, since an unclassified failure on a forge round trip really is the forge's — and they were reporting GitHub as broken for iterion's own stored key.
golden-master: a duplicate reference group is proved by a mutant, not by a note (#1009) (28af2c2)
why
The gate refused on ANY two byte-identical references, unconditionally. Its own message said the refusal is not always right — "on a refusal lane two entries legitimately capture the same 302, and the second is a control proving a mutant moved only the first" — and then offered no way to say so. The corpus answered in
note, which is prose, and the gate reads data. So a net whose duplicates were every one of them justified could never converge.
3.128.0 (2026-09-09)
Features
dsl: authoring lot 0 —
#comments, positioned diagnostics with fix lines, every doc fence compiled (#1010) (#1039) (b2067fd), references #936 #1012why
Outside a string, a prompt body or a
|block scalar a#never meant anything in the language; it was a lexer error the parser then reported as an unknown property named '#'. Measured on the repository's own documentation on 2026-09-09: of the 48 ```iter fences that failed to parse, 25 failed on exactly that — the maintainers reach for# noteas naturally as any model does, and the DSL quickref skill taught it in its canonical examples.
3.127.2 (2026-09-09)
Bug Fixes
forge: a deployment that moved could never repair its own hooks (#1032) (206a010)
why
Provisioning short-circuits when the bot set and the event set already match, and that test never looked at the address the forge is actually calling. But the hook URL is not a property of the request: it moves when the deployment's public URL moves, and when a connection starts or stops pinning a base of its own.
runtime: a node that failed still spent, and the run never booked it (#916) (de5bf14)
why
recordBudgetruns on the success path only. A node that failed returned its result beside the error — with the pass's cost stamped on it, which is what the delegate has always done and what two fixes today made reliable — and nothing read it. So the run's totals, the daily spend cap and a lending donor's ledger all missed whatever the failing node burned. On a long agent node that is a whole session, and the runs that fail are exactly the ones that burned the most.
3.127.1 (2026-09-09)
Bug Fixes
pipelines: the control center served a team the origin of its own fork (#1031) (a99fec7), references #871
why
The pipelines board is selected per team in cloud (cloudBoardResolve), but its bot was resolved tenant-free and launched by filesystem path — the fifth surface of the #871 class, and the last one still outside the tiered resolver. Two silent consequences: a team that forked a catalog bot got the CATALOG bundle on its own cards, and a bot only that team authored could not be carded at all (a stored row's Path is blanked, so MainFile() had nothing to launch).
3.127.0 (2026-09-09)
Features
3.126.0 (2026-09-09)
Features
3.125.1 (2026-09-09)
Bug Fixes
cost: an OpenAI turn was priced as if it had cost nothing to send (#1034) (6bb10e3), references #992 #992
why
Every OpenAI-family call through claw reported ZERO input tokens. Both endpoints reported the count and both translations dropped it: the chat-completions path parsed
prompt_tokensand never read it, and /v1/responses sendsmessage_startbare and built its usage from the output half alone. Sweeping claw for any assignment of input tokens returned two hits — the Anthropic SSE client and bedrock.
3.125.0 (2026-09-09)
Features
3.124.0 (2026-09-09)
Features
studio: redesign the cloud home around orchestration (#1028) (e5a711d)
why
CloudLanding is one of App.tsx's few eager view imports — PublicTopBar lives in the same module and renders on /marketplace, outside the lazy route tree. The redesign's static
import CloudHometherefore pulled the whole product page into the entry chunk: CloudHome + PlatformFeatures + StackCompatibility, ~40 lucide icon modules, 11 @lobehub brand icons and the 348-line cloud-home.css, downloaded and parsed on first paint by every authenticated operator — an audience AuthGate never shows it to.
3.123.3 (2026-09-09)
Bug Fixes
server: a tenant the store says is GONE is not a blip to launch past (#1027) (998baac), references #969
why
gateLaunch is the choke point every cloud launch surface crosses — the REST launch and resume, the inbound webhooks, the retry sweeper, the board dispatcher. It read the caller's team and, on ANY error, admitted the launch: quotas are operator policy, and a transient Mongo blip must not wedge a whole deployment.
3.123.2 (2026-09-09)
Bug Fixes
golden-master,modernize: a repairable certificate refusal no longer ends the run (#1007) (e8154c8)
why
Rf213cf.
lot_gate -> extension_provenance when forgedlands on aresumable: falsefail declared ahead of the repair loop, so every shape that setforgedended the campaign outright. The justification written above that edge — "dropping the act block breaks ledger_append_only" — holds for two of the five sites that set it, andledger_append_onlyishead_txt.startswith(base_txt): it pins only the text BELOW the run's base, so a block appended during the segment can be narrowed or…
3.123.1 (2026-09-09)
Bug Fixes
bots: a campaign verify node refuses a dirty tree instead of judging it (#995) (9b0e19b), references #807 #807 #799
why
A tool node whose whole contract is "judge HEAD" was judging whatever the previous attempt left on disk. Measured once: a golden-master gate ran 7,676 s until the pod's exec stream broke, the engine classified the failure NETWORK_TRANSIENT and re-executed the node on the same tree — where a mutant the harness had applied was still there. The second attempt judged a mutated program and called it the lot's; the run finished not-converged with hours of budget left.
server: an avatar recorded after a failed store write is iterion's fault, not the forge's (#993) (cf69634), references #969 #969
why
forgeUpstreamStatus returns 0 to mean "NOT an answer from the forge", and its own doc says the caller then answers with its fault status — "Only that arm may be a 500." The avatar route rendered that arm 502 Bad Gateway, so a persist failure AFTER an upload that had already landed on the forge was reported as a forge outage: the exact inversion the classifier was written to end, running the other way. Sentry, alerts and any client retrying on 502 were told a third party broke when iterion's own…
3.123.0 (2026-09-09)
Features
credentials,teams: an org can lend its own LLM keys, and a team has a lifecycle (#1000) (ebbae7e)
why
Sharing a key across an org's product teams had no home. The API-key walk only sees the team's and the user's rows, and secrets.OrgOwnerKey — despite its name — keys a TEAM forfait. The only way to share was to copy the credential into every team: N writes per rotation, N places to forget one, and no way to tell whose spend was whose. Measured on the prod instance, where one Claude forfait is already duplicated across two teams.
forge: a connection can pin the base its hook URLs are built from (#1011) (df80e5b)
why
Hook URLs are derived from the deployment's public URL, which is right for every connection until one of them cannot reach that host. GitLab refuses any webhook URL outside its instance-wide outbound allowlist with "Invalid url given" (HTTP 422), and listing a host is an administrative act on the forge's side, not ours. One such forge therefore pinned the public URL of the WHOLE deployment: moving to a new domain meant either leaving that forge behind or not moving.
3.122.3 (2026-09-08)
Bug Fixes
secrets,runner: a codex forfait refreshes itself, and an unrefreshable one stops being silent (#977) (bf05b07)
why
Nothing refreshed the ChatGPT (codex) forfait. The server's worker skipped the kind outright when no client id was configured, and the runner's per-run refresher handled only Anthropic, "left to the CLI / store worker" — which was in turn skipping it. Measured on a real deployment: a forfait last refreshed on 2026-08-29 was still being served on 2026-09-08, and the only symptom was a run failing its first LLM call with
401 Provided authentication token is expired, ten days and one layer away…
3.122.2 (2026-09-08)
Bug Fixes
server: a team-scoped write must land in the PATH team, not the caller's tenant (#997) (#1003) (d6d1fe7)
why
The auth middleware stamps ONE tenant — the caller's JWT — while authorization is checked against the team in the path, and canManageTeam deliberately admits a super-admin (or an org admin) on a team that is not their active one. When a handler forgot to re-scope, the row landed as (scope_team = path team, tenant_id = caller's team): invisible from both list endpoints, invisible to the target team's runs, and answered 201. The bot then ran without the credential it had been given.
3.122.1 (2026-09-08)
Bug Fixes
runner: a new generation erased the checkpoint it should have read (#990) (6f3926e), references #988
why
The workspace checkpoint is force-pushed to ONE ref per run, so the first push of a new runner generation destroys what the previous one left. That is harmless when the resume continued the same tree, and irreversible when it did not.
3.122.0 (2026-09-08)
Features
runs: expose persisted workspace checkpoint recovery (#988) (e4a7f60), closes #972
why
Surface the latest successful checkpoint event in inspection and unavailable commit listings, with provenance and a quoted fetch hint. Preserve no_baseline, final-bank fields and merge eligibility; report read failures and require validation of recovered work. Closes #972.
3.121.5 (2026-09-08)
Bug Fixes
test: persist Git fixture maintenance opt-outs (#987) (1b79f49), closes #974
why
Production Git commands invoked by tests do not inherit gittest.Cmd flags. Persist both opt-outs in the fixture common config and verify real Git resolution from source and linked worktrees, keeping a separate command-level control. Fixes #974.
3.121.4 (2026-09-08)
Bug Fixes
sandbox: a custom workdir over an oversized recipe streams the script, not a wrapper that re-embeds it (#967) (ac7b8d2)
why
Addresses R6a7f93. The custom-workdir path streamed
cd '<dir>' && exec bash -c '<script>'throughsh -s. That keeps the script off the HOST argv, but the in-pod shell then re-issues execve("bash", ["bash", "-c", "<script>"]) — and MAX_ARG_STRLEN applies to that exec too. E2BIG was relocated into the pod, not removed, for precisely the shape this streaming exists to serve: an oversized<shell> -c <script>combined with a non-default WorkDir.
3.121.3 (2026-09-08)
Bug Fixes
golden-master,modernize: an extension is acted by the net's subbot only — the gate knows it by the subbot's commits and certified blobs (#882) (e46e357)
why
Measured on a live campaign: a lot filed an extension request in one commit and acted it in the next — added the reference, appended the act block — and the harness's provenance rule, which refuses a request and an act introduced by the SAME commit, saw nothing; the lot's own file became a reference of the net that judges it, exempted as a pure addition, and the lot landed with a caveat.
3.121.2 (2026-09-08)
Bug Fixes
server: the reads that DESCRIBE a bot resolve the tier that runs it (#946) (#971) (8608ae5), references #871
why
#871 made every launch surface resolve
team -> platform -> baked, so a team's fork now runs on its board cards, triggers, schedules and webhooks. The reads that describe those launches stayed on platform-over-baked, so the same delivery ran the fork and was described by the origin — with no diagnostic, because both answers are well-formed.
3.121.1 (2026-09-08)
Bug Fixes
model: an in-process retry resumes the session the dead attempt opened (#958) (f713312), references #952 #912
why
When the LAST in-place attempt is the cheap one, the node reported the cost of nothing: attempt 1 spends an agentic session and hits a transient wall, attempt 2 cannot even spawn and returns an empty Result, and
result, err = fn()overwrote the figure with zero. The caps, the org monthly cap and a lending donor's ledger all read that figure.
3.121.0 (2026-09-08)
Features
observability: a node served through a facade says so on the run record (#926) (b3f7efb), references #474
why
With a tenant z.ai key, the claude_code delegate's default precedence routes every node through the Anthropic-shaped facade, which answers the requested claude id with the model it aliases it to. Declared and effective ids agree, model_drift stays silent, and the only trace was the _session_fingerprint buried in the node output. Measured 2026-09-07: three claude_code probe nodes declared claude-fable-5 / claude-opus-5 / claude-opus-4-8 all carried
facade:https://api.z.ai/api/anthropicand…
Bug Fixes
claw: a stale codex-cli never downgrades the ChatGPT identity, and a JSON Schema type array parses (claw b6e34a39) (#917) (34ea228)
why
The ChatGPT-Codex backend gates model availability on the
version:header; iterion resolved it from a hostcodex --versionprobe and let that value win over claw's baked release. A stale binary in an image then downgraded every OAuth call: measured 2026-09-07 on the cloud runner (codex-cli 0.139.0 shipped in the image), the backend answered "The 'gpt-6-astra' model requires a newer version of Codex" while the same model is served to the 0.144.6 release claw now presents.runview: list a run's artifacts from its artifact_index when the directory is not on this host (#919) (7bf70d8)
why
ListAllArtifacts walked runs/<id>/artifacts on the local filesystem and returned an empty list when the directory was absent — which is every run on a cloud server pod, since the directory lives on the runner that wrote it. Measured 2026-09-07: a run whose events carried two artifact_written entries (one of them a
publish:) answered{"artifacts": []}on GET /api/runs/{id}/artifacts while GET /api/runs/{id}/artifacts/{node}/0 served the body.server: a launch field the request does not declare is refused, not dropped (#949) (5ff01b3)
why
From the client, a parameter that was refused and one that was swallowed are the same answer: the request is accepted, the value does nothing, and the caller learns it from the behaviour of whatever it started rather than from what it was told.
3.120.2 (2026-09-08)
Bug Fixes
runtime: a queued raise_budget must reach the boundary that is about to kill the run (#938) (62829f3)
why
POST /runs/{id}/raise-budgeton a run busy inside a long node answers "queued … the run is busy in a long node and will apply it at its next boundary — it is not lost". Forbump_loopthat is true. For the budget it was false in the one case the command exists for.sandbox: a tool recipe too large for one argv element streams through stdin on kubernetes too (#937) (03370e7)
why
The kubernetes driver passed a
sh -c <script>recipe tokubectl execas a single argv element. Linux caps ONE argument at MAX_ARG_STRLEN (32 pages = 128 KiB) — a limit no ulimit raises — so a large interpolated recipe fails the fork with E2BIG before the pod is ever contacted:
3.120.1 (2026-09-08)
Bug Fixes
delegate: a delegation that died still names the session it opened (#952) (50d6574)
why
The claude CLI announces its session id on
system/init, the first thing it emits. It was logged there and dropped. A session that then dies mid-stream never produces a ResultMessage, and the failure path builds its result from that message alone — so the delegate returned a failure that could not name the session it had just spent minutes or hours filling.
3.120.0 (2026-09-08)
Features
credentials: accept a bare Claude setup token, and fingerprint the token (#948) (78b1957)
why
Three things a session paid for this morning, none of which the existing runbook answered.
Bug Fixes
platform-bots: four defects inside the shadow guard, found after #851 merged (#944) (7ad9695)
why
warnIfOverrideShadowsNewerBake fires for both origins — storedLaunchBot calls it for
teamrows as well asplatformones, and versionsBelow has a dedicated team branch — but the remedy baked into the message was unconditionally the platform one:iterion remote admin bots push bots/<slug>andDELETE /api/admin/bots/<slug>.
3.119.0 (2026-09-08)
Features
pipelines: Retry from zero — a board action that forces a FRESH run (#954) (27efc84), closes #496 #494, references #495
why
On a needs-attention card, Retry only restages the ticket and lets whoever claims it decide what "retry" meant: the studio's admission loop mints a fresh run, a live
iterion dispatchresolveslast_run_idand RESUMES the dead one from its checkpoint (resolveRunID -> LastRunForIssue -> resumableRunID). So on a dispatcher-owned board Retry is effectively Resume — beside a menu that already offers "Resume from checkpoint" as a separate, deliberate action. For a run that died in a way resuming…
Bug Fixes
runtime: cancel a node's whole process group, not just its shell (#935) (#955) (cd3a01b)
why
A tool node runs its recipe through
exec.CommandContext(ctx, "bash", "-c", …)without Setpgid, so cancellation killed the shell and nothing else. A job the recipe backgrounded survived, kept the inherited stdout pipe open, andcmd.Output()never returned: the run reported cancelled while the work it paid for ran to completion — burning wall-clock, a cloud pod, and writing the workspace that finalization was about to capture.server: a team's forked bot serves every launch surface, not only the manual one (#940) (6f8bcf1), closes #871, references #871 #946
why
resolveBotSource— the chokepoint the board dispatcher, the trigger spine, the cloud scheduler and the inbound webhooks all cross — hardcoded an empty team id, so the team tier ofteam -> platform -> bakedapplied on the studio button alone. A team that forked a catalog bot in the studio editor ran its fork by hand and the baked/platform bundle on every board card, trigger, schedule and webhook review, with no diagnostic: documented as functional while inert.
3.118.1 (2026-09-08)
Bug Fixes
runner: reserve the last usage-window retry for the authoritative reset (#922) (#953) (4e74590), references #684 #684
why
A usage-window retry arms on the EARLIER of the failed credential's own reset and the reopening of a credential the launch's walk passed over (#684). That earlier wake is speculative — the skipped credential may be refused too — yet it spends an attempt of the same budget: every arming
$incsretry_state.attemptsandScheduleRunRetryrefuses pastmax_attempts.
3.118.0 (2026-09-08)
Features
golden-master: an upload is a multipart request, and its boundary does not move (#902) (1921402)
why
A corpus that declares a file field had it flattened by
urlencode, which serialises a structured value through its repr: the application received a form field whose value was the TEXT of a Python object, refused the request for the wrong reason, and the reference recorded THAT refusal as the behaviour. An observation point that cannot express its own request observes nothing — and the lot that needed one stopped, correctly, rather than record two rejections as if they were the product.
Bug Fixes
sandbox: export the seeded forfait config dirs on the container env (#915) (167c40b)
why
The run's Claude Code / Codex forfait is delivered into the sandbox and seeded into CLAUDE_CONFIG_DIR / CODEX_HOME after start, but only the claude_code and claw delegates pointed their own spawns at those dirs. Every other process in the container — a tool node, a devbox script, a scanner driving the claude-agent-sdk — inherited the bare container env and ran unauthenticated while the credentials sat next to it.
3.117.0 (2026-09-08)
Features
dsl: warn (C249) when a branch-spawning router names one target twice (#934) (c122095)
why
fork -> adeclared twice on afan_out_allrouter validated clean and still does: the compiler keeps both edges, the run finishes, nothing says anything. Butfan_out_allspawns one goroutine per outgoing edge and derives every branch id from the TARGET (branch_<router>_<target>), so the two executions wear one id — they collapse onto one output slot at convergence and, since durable branch checkpoints, onto oneBranchCheckpointwhose cursor each goroutine overwrites, which lets a…
Bug Fixes
bundle,runner,server: a bundle may declare the engine it needs, and three surfaces refuse what they cannot run (#942) (4afe964), references #858 #881 #881 #858 #881
why
#858 layer 1. Layer 2 (C138, builtin arity at compile) shipped in #881; this is the complementary half — the arity check catches a builtin the evaluator does not KNOW, this catches everything else a newer engine brought.
3.116.5 (2026-09-08)
Bug Fixes
dispatcher: spare a run parked on a paused subbot descendant from the stall watchdog (#932) (4a953c0), references #558
why
A dispatcher-owned parent that reaches a
subbotnode whose child parks on a human gate blocks in runview.AwaitSubbotTerminal, polling the child's run record once a second. That poll appends no event, so the entry's watermark — fed only by DispatchSpec.OnEvent — ages past the stall timeout while nothing is wrong. reconcileStalled then interrupts the run, the retry re-enters the same engine, and a review left open over a weekend eventually burns max_attempts. A production deployment was working…
3.116.4 (2026-09-08)
Bug Fixes
platform-bots: a stored override that shadows a newer baked bundle is no longer silent (#851) (8ce6262), references #742 780/#785
why
A platform or team bot override outranks the baked catalog at every launch surface — that is the tier's purpose. The cost, unmeasured until now, is that a bundle pushed once keeps serving after a later release bakes a newer one for the same slug: the image moves, the bot does not, and nothing says so.
3.116.3 (2026-09-08)
Bug Fixes
board,forge,server: a failure must not answer like a success — three seams that could not report one (#928) (a650a51), references #891 #904 #893 #884
why
AggregateLabels() []LabelUsagehad no error to report a failure with, so boardmongo's only option after a failedlistAllwasreturn nil— and the studio label picker,iterion remote labelsand every vocabulary consumer read a transient Mongo failure as "this board uses no labels", with an operator re-creating labels that already exist.
3.116.2 (2026-09-08)
Bug Fixes
3.116.1 (2026-09-08)
Bug Fixes
golden-master: report broken selftest fixtures before indexing verdicts (#910) (6af47f6)
recovery,retrypolicy: two structural verdicts EXECUTION_FAILED promised could be outlasted (#921) (9a27477)
why
A provider that refuses the MODEL — an id no backend claims, a model the account may not use, one whose minimum client release the image is behind — answered about the caller, not about the request. No sample differs and no wait helps, so the automatic resume above it can only spend pods to be told the same thing.
3.116.0 (2026-09-08)
Features
golden-master: the two debts behind a 0/0 held-out figure are fields, not sentences (#901) (0d7ffe3)
why
The gate line checks
holdout_detected == holdout_total, which is vacuously true at 0/0. The report already said so — in a NOTICE, and this file's own comment names why that is not enough: "a notice string is where debts go to hide". One of the two debts got a field when it was measured (holdout_awaiting_gate); the other stayed prose.
Bug Fixes
runner,alert: a checkpoint compares the WORK, and a timer tick is not the run working (#906) (bddfcd0)
why
Two halves of one defect in the workspace checkpoint, found by the piloting session before the runner was even bumped — reproduced on a throwaway repository, three ticks a second apart.
3.115.3 (2026-09-07)
Bug Fixes
delegate: the two terminal returns that walked past the spend stamp (#908) (4e55cf0)
why
A delegation that ends badly still SPENT. The caps, the fallback chain's carried spend and a donor's ledger all read the cost from the output map, so a terminal return that skips the stamp records nothing — the money is gone either way, only the accounting disappears.
typedFailureexists as that choke point, and its own docstring says so; two returns walked past it.
3.115.2 (2026-09-07)
Bug Fixes
forge,server: a head repository the credential may not read is declared, and a forge refusal answers as the forge, not as an iterion fault (#903) (f47ff6e), closes #887 #888 #893
why
pkg/forge/gitlab's source-project lookup mapped BOTH 403 and 404 onto an empty headProject with a nil error. A permission ANSWER — the project exists, this credential may not read it — therefore arrived at every caller as the same value a merge request that names no source project produces, and the only thing standing between that and "therefore the base project" was that SameRepoAs happens to fail closed on an empty name. Nothing in the function said the result must not be trusted, and the…
3.115.1 (2026-09-07)
Bug Fixes
3.115.0 (2026-09-07)
Features
golden-master: the judge reads the declaration it was asked about, and the verdict says which one (#894) (7bc419d)
why
A net can declare a second ENVIRONMENT for the same corpus — a second database engine, a second runtime — and judging it means booting the app the other way and replaying the SAME references. Until now the harness opened
config.jsonin two places, hard-coded, so a gate command passingGM_CONFIG=<other>.jsonran a judge that never read the variable.
Bug Fixes
sandbox: never pass kubectl --request-timeout — it discards the in-cluster config and every apply dials localhost (#899) (d9a3a7c), references #823
why
Deployed to production at 18:27Z and rolled back at 18:50Z: with the flag set, every kubernetes sandbox creation fails at "apply file secrets secret".
3.114.0 (2026-09-07)
Features
runner: a copy-based sandbox's work leaves the pod while the pod still answers (#898) (f111dd4)
why
On a driver whose workspace is a tar COPY inside a pod, nothing a run produces leaves that pod before teardown: the export runs once, at the end, and every push the runner performs reads the exported clone. A pod that dies hard therefore takes the whole run with it, however well the run committed.
Bug Fixes
board-sync: a human's move out of Blocked on the roadmap board is the reopen (#895) (169be9d), closes #839
why
The terminal sink (ADR-096 §5) protects a card from a MACHINE resurrecting it. A drag on the bound GitHub board is not a machine: it is the operator's hand, arriving through the only channel they have. The project pass refused it all the same, so a card moved Blocked -> Inbox in production stayed
blockedfor ever while the roadmap showed Inbox, with one server log line to say so.
3.113.3 (2026-09-07)
Bug Fixes
bots: lot_verify reads the oracle report with one decoder pass per opening line — the enclosing object wins, a named report never loses to an unnamed one; no guessed block start, no budget (#865) (0c48563), references pre-#815
why
Revi (R46d257, medium) on the bounded scan: the block's start was guessed among the two nearest opening lines and the window's FIRST one, so an indent=0 report (a list's objects at column 0) preceded by any brace-first line was lost and a green oracle typed ORACLE_NOT_RUN. The guess is gone: json.JSONDecoder().raw_decode(window, idx) parses in place from each "{" line of the window (lstripped: a logger's indentation leaves a block without a start otherwise), from the last upward; a noise line…
review-pr: the run budget follows the measured workload — $48, sized on two deaths at $36 and $30 and the engine's 90% wall (#868) (a1e76e4)
why
Measured: the merge gate died twice on one revision of a 7-file PR, budget exceeded at cost_usd 36/12 then 30/12 on the automatic relaunch, and posted no verdict; a second PR of the same size died the same way. The cap was sized on a 2026-09-03 sample (median $3.6, p95 $10.5); the review that reproduces its findings and answers the fixer's rounds costs three times that now. A required check that dies is worse than one that costs: the cap follows the workload, the per-run --max-cost-usd override…
runner,runtime,dsl: a deterministic failure is never auto-resumed, every failed run says why on its timeline, a run records the build that launched and ran it, and a stacked PR clones its base (#881) (02b928a), closes #859 #857 #697 #880, references #858 #850 #774 #697 #697
why
Function calls parse generically, so a
computeexpression calling a builtin with an argument count that builtin does not accept compiled clean and only failed at run time. On a cloud launch that costs a sandbox, a clone and a plan phase before the failure is even visible — and the failure is deterministic, so nothing about retrying it helps.
3.113.2 (2026-09-07)
Bug Fixes
ultracode: the model gate admits the Claude 5 family, shared by the compiler and the studio endpoint (#780) (9d39bc5)
why
reasoning_effort: ultracodewarned C089 on every model but Opus 4.8, and the studio's effort-capabilities endpoint offered the mode on Opus 4.8 alone — two copies of the same predicate, written when Opus 4.8 was the only model carrying the orchestration half. The Claude 5 family (Opus 5, Fable 5.1) carries it too, and the campaign bots now default to claude-opus-5: on them ultracode compiled with a stale warning and the studio never offered the mode. The prerogative itself was never gated on…
3.113.1 (2026-09-07)
Bug Fixes
dsl,boardmongo: a group param expands in one pass, and a cascade budgets one round-trip per write instead of the whole sweep (#889) (565ae40), closes #879 #883
why
The bind loop called strings.ReplaceAll once per key, so a bound value containing
{{params.<other>}}text was re-expanded on a later iteration, and Go's random map order decided which value won: two compiles of the same .bot could produce different node commands, edge conditions and loop caps.
3.113.0 (2026-09-07)
Features
modernize: a lot QUALIFIES a moved reference before asking to re-record it (#890) (21f4839)
why
The net stays off-limits and a moved reference stays a regression until proven otherwise; what the doctrine did not say is that naming WHICH of three things moved is the lot's job, and that the gesture differs:
3.112.25 (2026-09-07)
Bug Fixes
forge,server,credentials,runtime: a 404 is typed by resource, a publish grant refuses the launch it cannot serve and dies with its run, and a squash message stays in the run range (#886) (1178fa6), closes #812 #826 #820 #747
why
Every provider's 404 collapsed onto ErrHookNotFound at the one shared mapping point, so a pull request that does not exist reported "forge: hook not found" — and GitHub answers 404, not 403, for a resource a credential may not see, so a token short of pull_requests got the same webhook message. StatusErr now types a 404 by OPERATION: the hook operations keep their sentinel (deprovision reads it as "already gone"), everything else becomes a *NotFoundError naming its own call, and every 404…
model: ChainHooks forwards every EventHooks field, and run.* resolves in the single template pass (#878) (2b4510f), closes #836 #817
why
ChainHooks composed 18 of the 22 func fields and dropped OnAssistantText, OnUsageCap, OnUsageProgress and OnOrchestrationStall. runview chains every ExtraHooks entry through it, so any run launched with one — the Prometheus exporter (ITERION_PROMETHEUS_ADDR) is the shipped case — lost those four event families silently: no live usage_progress for a supervisor's cost_gt monitor, no usage_cap, no delegate_stall, no assistant text in the timeline. Nothing failed; the callbacks simply never fired.
sandbox: every setup phase is bounded on both drivers, a tool_result crossing the IPC is clamped, and a promise on a dropped bind is withdrawn (#877) (186d434), closes #823 #837 #834, references #815
why
Two setup phases still ran on the bare run context, so a stall in either held the run with no
sandbox_startedevent, no typed failure and no redelivery until the outer max_duration fired.webhooks,gate,branch-improve-loop: a fixer stops when its PR closes, never pushes onto a merged one, and a gate that cannot decide fails closed (#875) (f334053), closes #847 #831 #863 #773, references #863 #855 #783 #788
why
Four defects in the inbound lanes, all measured on production deliveries.
3.112.24 (2026-09-07)
Bug Fixes
golden-master: a stat-recorded path is stat-compared whatever today's size bound (#873) (6164e1a)
why
Third review round of the residue follow-up. The legacy migration of a "st:" record raised the size bound in the same change: a record written under 8 MiB for a file that hashes under 256 MiB today could never match — an untouched operator file in that band was permanent residue, the very stop the migration was written to prevent. The comparison is scheme-aware now: a stat record is answered by the stat form of the path whatever the current bound; the legacy branch migrates digits only.
server,trigger,cloudsched: the trigger spine and cron schedules launch through the org gate, and an emit meters what it fans out to (#872) (a6cec38), closes #844
why
Three cloud launch surfaces sat outside gateLaunch, so an org launched past its suspend, its concurrency cap, its launch rate and its monthly run/cost caps, unmetered.
3.112.23 (2026-09-07)
Bug Fixes
delegate: the Workflow tool is withheld from every non-ultracode claude_code node — the reviewed content can no longer arm a multi-agent orchestration (#869) (9fbb5f7), closes #867, references #780 #785 #788 #780 #785
why
Claude Code arms its multi-agent Workflow tool on the word "ultracode" anywhere in the prompt, and a node's prompt carries the content it works on. Four revi/review runs on 2026-09-05 (PRs #780 and #785, both ABOUT ultracode) each launched one or two background Workflow orchestrations from a plain
high-effort judge node, reached 1.2–3.2 M session tokens and died at $14–63 against a $12 cap — the data switched the node into a mode the operator's effort never granted.
3.112.22 (2026-09-07)
Bug Fixes
golden-master: a type change is a dirty path, a big file's fingerprint round-trips, an unreadable subtree is undecidable (#866) (17681ca)
why
Sixth review round. The porcelain status set lacked T, so a type change (a tracked file swapped for a symlink) was neither recorded before the mutant nor seen after a revert that left it: the one disposition that lets the gate through, on the fail-open this PR closes. T is a status.
3.112.21 (2026-09-07)
Bug Fixes
golden-master: a revert that says 0 without restoring leaves a residue the sweep names and stops on (#856) (3ececd4)
why
Revi's open question on the fourth round: "reverted" let the gate through whenever revert.sh exited 0, even when git status still showed changes; the note said so, nothing stopped, and record mode has no dirty check — an under-specified revert.sh would have references sealed over the residue, the harm the sweep runs in record mode to prevent.
pluginsource: the rename is the test-and-set, so an immutable tree is never retired on a stale read (#862) (a05060a), references #854 #822
why
The early-accept added for #854 is a READ, and the retire it was meant to prevent is decided two syscalls later — nothing serialises them across publishers. Replay with pinned A/B/C over one cache dir: C reads
destabsent; A renames its tree in; B's rename loses ENOTEMPTY and enters the fallback; C'sos.Statnow sees A's tree and retires it, sodestis ABSENT; B's read finds nothing and returns the ENOTEMPTY cause. The exact message that ejected #822 from the merge queue, still reachable.
3.112.20 (2026-09-07)
Bug Fixes
bots: lot_verify's report search is bounded — a chatty stdout with no report is typed in seconds, not minutes (#852) (e6a1f12)
why
Measured by the reviewer on the shipped function: for every stdout line ending in
}the search retried a parse against every earlier line whose lstrip started with{, re-joining the slice each time — 30 s of CPU for 2000 lines of 33 characters, 86 s at 500, linear in line length, on the very no-report path the reader exists to type.
3.112.19 (2026-09-06)
Bug Fixes
server,cloudpublisher: the board dispatcher launches through the org gate, a run no credential tier can fund is refused at publish, and a launch give-up reaches the needs-attention lane (#850) (0841f18), closes #841
why
A card the dispatcher gave up on before any run existed (the launch attempt cap) carried a give-up stamp with no run id, and GiveUp.Current was bound to a run id, so the pipeline board filed the card among the done tickets: a card nobody could launch was invisible. The stamp now carries a
launchmarker — current for the card in its state whatever run it points at, part of the stamp's identity, round-tripped by both store twins — and the ticket card (or the run card of an older attempt) takes…
3.112.18 (2026-09-06)
Bug Fixes
runner,runtime,sandbox: a stale launch redelivery never restarts a requeued run, the resumed budget is re-stamped post-clamp, and the kubernetes post_create phase is bounded (#822) (193585f), closes #714 #718 #719 #723 #720
why
A
queueddoc is the one status runResolveDoc restarts from the entry node, so a redelivery that reaches it from an earlier life of the run re-spends everything the checkpoint exists to save. The admission gauntlet now reads the queued arm explicitly: a launch message published before the doc's current QueuedAt belongs to a finished attempt and is dropped (the attempt now queued carries its own delivery), and a queued doc holding a checkpoint resumes instead of restarting.
3.112.17 (2026-09-06)
Bug Fixes
pluginsource: a peer that published the same pinned checkout first has published this fetch's tree (#855) (9144328), closes #854, references #822
why
Two fetchers sharing a cache dir both stage the same key; the first rename(staging → final) wins and the loser's fails ENOTEMPTY. The loser already re-read the final path and accepted it — but that read could find NOTHING, because publish renames an existing tree ASIDE before its own rename. With N publishers the sequence is: A publishes, B's rename loses, C retires the path to make room for its own copy, and B — reading between C's retire and C's rename — sees no final path and reports…
3.112.16 (2026-09-06)
Bug Fixes
delegate: a terminal verdict on the formatting pass is returned, not retried; a structured answer is an answer; a 403 is a refusal (#845) (8202bcf)
why
Third review round of the facade-render fix. The formatting loop assigned every rendered verdict to fmtErr and retried the attempt once, so a dead credential, an unavailable model or an exhausted window bought a second CLI spawn against a provider that had just refused, and re-filed the same usage evidence. Only the transient class is retried now (renderRetryable); the rest returns typed at once, as the recovery pass already did.
3.112.15 (2026-09-06)
Bug Fixes
server: a refused launch gives the board card back and retries with a backoff; the attempt cap files it blocked with the reason (#840) (558325b), closes #814, references #839
why
Issue.LaunchRefusal (attempts, last reason, last instant, not_before) is the cloud dispatcher's retry bound for a claimed card whose launch the run service refused before any run started. Written fenced (SetLaunchRefusalOwned, a BoardStore contract method on both twins), kept across the give-back transition, cleared by a stamped run (SetLastRun / SetLastRunOwned - a launch happened) and by an operator Reopen. Coordinator.ListDispatchable skips a card until its not_before, in the query, for the…
3.112.14 (2026-09-06)
Bug Fixes
billy: a delivery reserve out of the duration cap, a typed decline the platform honours, a structural drift gate, and no masked exit status in verify.sh (#830) (5166823), closes #789 #779 #705 #706, references #770 #705 #706 #779 #789
why
has_drift_gate read the verify script one line at a time and counted a quiet diff only when the failing exit appeared on the SAME line, so the commonest real shape -- a multiline "if ! git diff --quiet ...; then ... exit 1; fi", which is what this repo's own openapi:check target writes -- was rejected. Run 01a072b5 delivered eight commits, logged VERIFY OK, and still returned exit 3 / DRIFT GATE MISSING, leaving the merge gate red on a green tree.
forge,webhooks: one App client per connection with its real slug, tokens minted from the recorded grant, a 403 that arms the preflight, a connection-first reply gate, and a GitLab fork MR that names its source (#846) (e2b5fe7), closes #711 #710 #717 #708 #728, references #711
why
Two AppClient gaps that share a fix site.
golden-master: the harness reverts a mutant an interrupted gate left applied, at the start of the next (#807) (7a70bbc), references #799
why
A stream cut, a SIGTERM or a pod kill between apply.sh and revert.sh leaves a mutant in the tree. The next gate on that tree judged a mutated program and called it the lot's: measured on a verify node retried on the same tree after its exec stream broke at 7 676 s — the oracle reported the mutant's file as "not committed", the build gate went red on a package that exists on the bank, and the run finished not converged with hours of budget left.
server: a publish grant must belong to the run that carries it (#849) (0aab3d2), closes #825
why
The publish token is a launch VAR, and injectForgePublishVars honours a caller-pinned one. Every reader then proved the grant SELF-consistent — its connection belongs to its team, its repo matches the pull request, its host matches the connection — and a grant minted for another tenant passed all of them. None asked whether that tenant was the run's. So an authenticated operator of team B holding a team-A token could have iterion comment on team A's pull request, write team A's REQUIRED commit…
3.112.13 (2026-09-06)
Bug Fixes
server,store: the pr_url launches pass the fork guard, a typed PR-closed end reason, a claimed launch_error retry, and gate notices that name a remedy that can reach the run (#824) (463394b), closes #683 #702 #722 #721 #713 #715 #712, references #713
why
applyPRLaunchContext — the door the studio launch, the remote CLI/MCP launch and the cloud board coordinator use to stamp a PR onto a run — carried no fork guard, while the five webhook lanes were made fail-closed by #683. Same launch pair (<base>.CloneURL + the PR's head branch), so a head branch that does not live in the base repo could be checked out against a same-named branch of the base repo and a code-pushing bot would commit onto it.
3.112.12 (2026-09-06)
Bug Fixes
runner: the per-run clone's git commands leave no maintenance process behind (#829) (da8a2f0), closes #821, references #813 #821 #828
why
The merge-queue build of #813 ejected the PR on a cleanup, not an assertion:
TempDir RemoveAll cleanup: unlinkat …/deploy-onyxia-…/.git/ objects: directory not empty— something was still writing under the plugin-source cache checkout after the test had passed.runtime,bots: an oracle that never ran is not a RED — the run-files env var goes with its bind, the report goes to a temp file of the run (#815) (f517637), references #795 pre-#795
why
Measured on the pod backend, four modernize lots in one night: gate green, references intact, and "oracle RED" on one line — the gate wrapper's
cannot create /iterion/artifact-files/gm-last-report.json: Directory nonexistent. The harness never ran: the redirect died first. One lot diagnosed it and blocked, two were stopped by the loop budget guard, one spent 173 minutes repairing an environment. Some 20 run-hours, no verdict.sandbox: a sandboxed claw node relays its tools, retries and turns (#835) (e6c4b40), closes #811, references #805
why
The
__claw-runnerrelay carried the LLM steps (#805) and nothing else, so everything the in-container loop observed died at the container boundary: the studio timeline of a sandboxed claw node showed LLM steps and no tools, an in-container permission denial left no audit, a supervisor'stool_*monitor never armed, the plan checklist stayed empty, a retry or a compaction round read as a silent gap, anditerion fork --turnhad no anchor at all.server: a live 401 marks the connection revoked; only a forge that answered "no" earns the vouch (#833) (5cd4877)
why
A failed WhoAmI now takes three shapes instead of one. A credential the forge rejects outright is the reconnect problem the revoked-status rung already names — a 422, forced or not, because no force can carry an upload the forge would refuse the same way. A forge that answered but would not describe the account (insufficient scope) is the one case the operator can vouch for: a 409 unforced, tolerated under force. Anything else — a spent budget, an unreachable forge, a 5xx — is not an answer: a…
3.112.11 (2026-09-06)
Bug Fixes
git,pluginsource,runview: a git command iterion runs into a directory it disposes of leaves nothing running (#827) (cdfdc65), closes #821, references #813
why
The merge-queue build of #813 ejected the PR on a cleanup, not an assertion:
TempDir RemoveAll cleanup: unlinkat …/deploy-onyxia-…/.git/ objects: directory not empty— something was still writing under the plugin-source cache checkout after the test had passed.
3.112.10 (2026-09-06)
Bug Fixes
forge: the GitHub-App client serves the pull/CI API on scoped tokens — the card PR panel works on App connections (#809) (89fafb5), closes #777
why
forgeAdminFor yields a forgegithub.AppClient for a github_app connection, and that type did not implement forge.PullClient, so pullClientForConn failed its assertion: GET|POST /api/v1/native/issues/{id}/pulls, …/pulls/{n}/ci and …/pulls/{n}/merge answered 501 on the connection kind the connect wizard creates by default, while the same card worked on a PAT connection.
3.112.9 (2026-09-06)
Bug Fixes
delegate: a facade's bracketed API-error render is an API error, not a node's answer (#818) (6930fd8)
why
Measured on a golden-master run behind an Anthropic-shaped facade: the campaign node finished in 2.5 minutes with its structured output built from "API Error: [500][Operation failed][<request id>]" — the CLI's render of an upstream 500 — and the graph continued: 283 minutes and the next node's budget spent on a request that was never acted, the run ending unconverged.
3.112.8 (2026-09-06)
Bug Fixes
secrets,server,cli: the OAuth token endpoints are env-overridable, a refused personal OAuth credential is audited, and
iterion secret setshape-checks what it stores (#819) (ca46d33), closes #725 #726 #727why
The Anthropic forfait's authorize URL, redirect URI and scopes each read envOr("ITERION_OAUTH_FORFAIT_ANTHROPIC_...", default). The token endpoint the auth-code exchange and the refresh worker both POST to was a bare const, under a comment promising the same per-deployment override — so an OEM-repackaged CLI or a proxying deployment could move three endpoints of four and keep refreshing against the vendor's host, silently.
3.112.7 (2026-09-06)
Bug Fixes
server: the board dispatcher never claims a card it cannot launch, and a column iterion wrote on its own authority is not reflected onto the bound board (#813) (9a4a6a0), references #798 #798 #798 #798
why
Two store-level seams the #798 fix rests on. Coordinator.ListDispatchable is the cloud dispatch tick's candidate query: unclaimed cards in a launch column THAT CARRY A BOT, filtered in the query because the batch is capped and bot-less cards (never written, so the oldest) would fill every batch. ListEligible stays the sweeps' listing. Issue.StateReason persists the provenance of the card's last transition - the same value its state event carries, derived once by native.StateProvenance - so a…
3.112.6 (2026-09-06)
Bug Fixes
dsl,runtime: three silent constants — run.* in every data mapping, a typed compute int, outputs.* in tool commands (#816) (0b2138c), closes #791 #792 #797
why
resolveRef's RefRun arm served
idand nothing else, so a fail node'smessage:(and an edgewith, anemitpayload, a subbotwith:) rendered{{run.max_cost_usd}}/{{run.elapsed_seconds}}as an empty string with no diagnostic — C029/C036 accept the reference, the resolver dropped it. The very message a budget guard wants ("planning used X of Y, raise --max-duration and resume") could not be written fromrun.*.server: a forge that will not describe the account asks for the operator's word, not a 502 dead end (#808) (bd94b5a)
why
An avatar apply on a connection of unknown kind asks the forge who the token is. When the forge refuses to say (a token without the scope, a forge without the field), the unforced apply answered a bare 502 that recorded nothing — so the studio card, which now only offers the vouch on a 409, repeated the identical 502 on every click and the forced apply the server still supports was unreachable from it.
3.112.5 (2026-09-06)
Bug Fixes
runner: a sandboxed claw node meters from its relayed steps, on the credential that served it (#810) (dc76093), closes #805, references #805
why
A sandboxed claw node prices its call inside the container, from a cold process where neither the live registry nor the aggregator has a cache yet, so the committed table is what it falls back to — and gpt-5.6-sol, the model the campaign bots' cross-family plan review runs on, had no entry: 24 of the 33 plan reviews served on ovh-prod in the last week carried no cost_usd at all (#805).
3.112.4 (2026-09-06)
Bug Fixes
runner: an IR this runner cannot load is a verdict on the run, acked — not eight silent redeliveries to the DLQ (#806) (b693fd0)
why
Measured on a five-release server/runner skew: six runs (five of one team, one of another) went failed_resumable within 75 s with zero events and an empty final_error; the runner logs read "compile IR: N diagnostic(s)" on every delivery, 1 … 8, then "parking on DLQ". The compiled IR of a server ahead of the fleet does not load on the runner, the error came back generic, and the delivery loop did what it does with generic errors — while the run said nothing.
3.112.3 (2026-09-06)
Bug Fixes
server: a forced apply bails when /user never answers; the card vouches only on a 409 (#804) (66e63b5), references #803
why
Revi's gate on #803 (R77fadf, R4a0ce8) and its assumptions: - Under force, a WhoAmI failure was swallowed even when it was the apply's own deadline expiring — the upload then ran on a dead context and stamped "context deadline exceeded" on the connection, blaming the avatar for a forge that does not answer. A genuine refusal is still tolerated; a spent budget bails with the accurate reason and records nothing. - The card sent force:true for every account not flagged as a bot, so the 409 branch…
3.112.2 (2026-09-06)
Bug Fixes
server: the avatar record rides its own budget, not the round-trips' deadline (#803) (75e8d0e), references #800
why
Revi's gate on #800 (R8b0bd7): giving the apply one bounded context put the outcome record under the same 20 s deadline as the upload — so the slow forge, the failure worth recording, expired the context first and the record never ran (and an upload landing near the deadline answered 502 for an avatar that was live). The record now takes a 10 s budget of its own, detached from the round-trips; a forge that hangs is proved to leave its reason on the connection.
3.112.1 (2026-09-06)
Bug Fixes
runtime: resolve the template snapshot on every dispatch path — a fan-out branch, the llm router and llm_or_human rendered {{run.}} and {{outputs.}} as literals (#796) (72275c0), closes #763, references #763 #763
why
model.WithRunID/WithTemplateDatawere attached on the trunk dispatch path only, so the SAME node rendered two ways depending on how it was reached. Inside afan_out_all/fan_out_eachbody a prompt kept its literal{{outputs.x.y}}braces and a toolcommand:substituted an EMPTY string for{{run.id}}— a silent constant, not a visible failure. The llm router and thellm_or_humanLLM half never had the snapshot either.
3.112.0 (2026-09-06)
Features
chart: a priorityClassName for the server and runner Deployments (#802) (8a22cc7)
why
The platform pods (server, runner) and the sandboxed run pods carried no PriorityClass, so a run pod bursting on a node committed at 99 % of its requests was a peer of the runner that owns its run for the scheduler and for eviction.
server.priorityClassName/runner.priorityClassName(empty = the cluster default) let a deployment rank the platform above the run pods it starts (SocialGouv/iterion#732: prod sets both to the cluster'sresource-burstable; the run pods stay unclassed).
Bug Fixes
board: derive the degraded readout from the binding, not from one pass (#787) (cfaaa62), closes #775, references #793
why
ReconcileStatusOptions reported a LostColumn only on the pass that observed the loss: both append sites were guarded by
id != ""and that same pass dropped the cached id. From the next pass on the state had no id, sorep.Lostwas empty andrep.Reason()was "" — and the caller, reading "nothing lost", cleareddegradedon the first unrelated adoption. The lost column then read healthy forever while every reflect onto it still refused, counted inreflect_no_columnand invisible…runtime: the loop budget guard's warning says its rule (#801) (1dfe7ec)
why
A declined back-edge emitted budget_warning {remaining, needed, used, limit}; read on a run with more remaining than needed, it did not say why the loop stopped — the rule is that the next iteration would land at or past the 90 % threshold where the engine refuses to start any node, and remaining > needed does not contradict it. Measured on a run whose repair loop was declined with remaining 15 644 s and needed 13 154 s: used 13 156 + needed 13 154 = 26 310 ≥ 25 920 (90 % of 28 800). The event…
3.111.1 (2026-09-06)
Bug Fixes
server: a connection older than account_kind learns it before the avatar gate (#800) (d7e4138)
why
Revi's gate on #794: the runbook handed the PIC operator the non-forced avatar command for connections that predate AccountKind — every one of them answered 409 needs_force, because the field is written at connect time only. The apply now asks the forge who the token is when the field is empty and records what it learns, so the bot gate judges the account and a group-token bot user needs no --force; a person's PAT still does.
3.111.0 (2026-09-05)
Features
brand: the iterion-bot mascot everywhere — forge bot avatars, favicons, app icon, docs logo (#794) (1d0661d)
why
The official iterion-bot GitHub account wears the mascot; the product still wore the hexagon, and every favicon/app-icon/logo copy was a hand-dropped file with no source of truth.
3.110.0 (2026-09-05)
Features
trigger: an effect KIND on the outbox — a native card move reaches the bound GitHub board through the durable outbox, the pass stays the net (#793) (7d01723)
why
The outbox row gains a discriminator so an effect that is NOT owed to a subscription can ride it:
launch(ADR-094's original) andprojection(ADR-097 §10's named follow-up).
Bug Fixes
runtime: a loop is priced on any entry into its body; a stale run-start mark is re-based on resume (#783) (d69189a)
why
The loop budget guard prices the next iteration by the last one, from a mark set when the loop is entered from outside. That mark was only set when the body was entered at the loop's HEAD (loop.Entries). A campaign bot's extension loop is entered elsewhere: its body shares the verify and gate nodes with the lot's own repair loop, so the run reaches it at verify, off its head, and the loop kept the mark the session baseline had set at run start. Its first crossing was then priced at everything…
3.109.0 (2026-09-05)
Features
bots: the plan-budget guard reads the run, and every coded refusal reaches the run as a typed fail (#790) (044904c)
why
branch-improve-loop's plan-phase guard measured its own wall clock in
plan_scope_probe(astarted_epochstamp) and compared spend against two vars mirroring thebudget:block by hand. Both were workarounds for primitives that did not exist; both are now defects. A mirror var is not reached byiterion run --max-cost-usd 200, so a re-budgeted run kept refusing against the literal 75 nobody had updated — the guard's arithmetic silently disagreeing with the budget the run was actually under.
Bug Fixes
engine,bots: the engine's script and the judge's report stay out of the judged tree (#795) (7a792ac)
why
An extension act by pure addition, correct in content, was refused by the net's verify for three untracked paths the acting agent never wrote:
.iterion-script-*.py— the tool node's own script, created by the executor at the workspace root so a bind-mount sandbox can see it — and.golden-master/.last-report.json— the gate wrapper's report, defaulting inside the net — beside the skills the engine mirrors. The gate that judges the tree's cleanliness read the engine's instrument and the…
3.108.3 (2026-09-05)
Bug Fixes
forge: the GitHub-App client serves the issue API on scoped tokens — the forge→board issue sync works on App connections (#776) (0025229), closes #781
why
forgeAdminFor returns a *github.AppClient for a github_app connection and that type carried only CommentIssue, so
admin.(forge.IssueClient)failed on the connection shape the studio's connect wizard creates by default. The forge->board sync answered 502 "provider github has no issue client" on demand and warned it every 5 minutes in the worker, so a bound team's cards were never hydrated and the ADR-097 project pass read skipped_no_card for every item, pass after pass. The autofix lane's…
3.108.2 (2026-09-05)
Bug Fixes
runner: retain exhausted lock deliveries in the DLQ (#770) (c804064), closes #703
why
Delay lock contention by the configured lease interval and retain the final delivery in the DLQ with a durable event. Closes #703.
3.108.1 (2026-09-05)
Bug Fixes
cli: a subbot child on the CLI host executes in the parent's sandbox and in the parent's workdir (#778) (1a781bb), references #766
why
#766 made a child execute in its parent's sandbox on the cloud runner and in the studio's in-process service — and left the third host out: the CLI's subbot runner built the child engine without the parent's sandbox facts and without a workdir, so the child defaulted to the process cwd (engine.go: workDir defaults to os.Getwd() at Run time). A parent that swapped to a per-run worktree therefore handed its child the BASE tree: whatever the child committed landed in a tree the parent's gate never…
3.108.0 (2026-09-05)
Features
dsl: a node reads the run's budget through run.*, and a fail node carries a typed code, message and resumability (#764) (34a6850), closes #738 #739, references #737 #695 #670 #760
why
A node that wants to reason about the run's budget -- "planning has used a third of max_duration, stop planning" -- had nothing to read: the
runnamespace resolved onlyrun.id, and thebudget:block's caps were compile-time literals. PR #737 had to self-measure wall-clock in a tool node and mirror the budget through two hand-maintained vars that drift from the block in silence; every phase-budget guard would have repeated it.
3.107.0 (2026-09-05)
Features
golden-master: sync-harness.bot — the judge's code reaches a target tree without a rite (#750) (12e35c6), references #765 #765
why
The harness is the net's decision procedure. Its content belongs to the net's owner and changes through a rite; its code is this bundle's and is repaired upstream — and until now the only path for a repaired judge into a tree that judges with it was a full rite: hours of agent work and a fresh held-out cycle for a one-line fix in a file no agent may edit. Measured on a live campaign: a rite re-materialised the harness with the bug still in it, and every lot on that tree was refused on the same…
3.106.1 (2026-09-05)
Bug Fixes
board: re-land #745's round-4 fixes the merge queue dropped, plus the round-5 findings and #759 (#772) (ef7b8c1)
why
SetStateFrom answers a card that drifted between the read and the write with (issue, changed=false, nil) — the operator got there first, which is not an error. The project import discarded that flag and read the nil error as success: it counted a transition the store never made, skipped the reflect, and recorded the board's status as synchronized, which makes decideProjectStatus a no-op from then on. The periodic worker repairs that on a later pass; the one-shot
iterion issue import --project…runtime,runner: a subbot child executes in its parent's sandbox — a pod of its own lost its commits (#766) (b272045)
why
Measured on the first subbot to run on a cloud pod: under the kubernetes driver the child engine started a sandbox of its own, the driver copies the workspace into a pod, so the child's commits lived in its copy and died with it (final_branch null, the commit unreachable from anywhere) while the parent, parked on the subbot node, resumed and re-judged an unchanged tree. A net's extension loop cannot converge that way. On the docker driver the same code happened to work: a second container…
store: protect run saves with document version checks (#771) (09fe07f), closes #701
why
Advance versions on partial writes in both stores; protect rename and rewind, and reload queued transitions before saving metadata. Preserve legacy documents and destination versions during migration. Closes #701.
webhooks: acknowledge authorization outages without launching work (#768) (d2d0def), closes #704
why
Retain the forge failure in the delivery audit and return HTTP 200 across the command, conversation and review request lanes. Closes #704.
3.106.0 (2026-09-05)
Features
bots: the campaign fleet plans by default, refuses a missing repo typed, and carries Persy (#761) (7aa77ff), references #751 #752 #619 #751 #752 #619
why
The seven campaign bots (feature-dev, feature-gap-fill, branch-improve-loop, whole-improve-loop, test-coverage, e2e-coverage, app-dev) keyed their plan phase on plan_review, which ResolvePlanReview answers off on every single-provider deployment - so the commonest setup never planned, silently, under a var named after a review (#751).
Bug Fixes
golden-master: the certifier reads a request in the re-baseline ledger's spelling too; an act already at the base is not an extension (#765) (21ee24f)
why
Measured on the first extension request to reach the additions-only verdict in cloud: the request had been written as the ledger's own header taught —
expected_pathsandentries(the re-baseline idiom a worker had copied into the header) — whileextension_verdictread onlypathsandcorpus_entries. Every conforming request was therefore "smuggled": "1 added entry no acted request claims", "refs/<id>.txt is neither declared in the request's paths nor derived from a claimed corpus…
3.105.0 (2026-09-05)
Features
board: sync a team's GitHub Projects v2 board with the native board — ADR-097 (#745) (3e991ce), references #2 #1 SocialGouv/iterion#613
why
AGENTS.md makes the Projects v2 board the roadmap truth and the native board the bots' operational surface, but nothing joins them: the board's Status/Area/Mode/Priority live in the GraphQL API, and no seam in iterion speaks GraphQL to a forge. So a human's "In progress" never reaches the dispatcher and a bot's "done" never reaches the roadmap.
3.104.2 (2026-09-05)
Bug Fixes
modernize: every refusal is a verdict the graph fails on; the gate's commit is HEAD plus one line; a timeout fails before any subbot (#757) (7503e2f)
why
Third review round (two medium, three open questions), each reproduced and pinned.
runtime: elect a fan-out collector from predecessors inside the fan-out only (#758) (2f69832), closes #741, references SocialGouv/iterion#741 #741 #741 #741 #741
why
A fan-out target that is ALSO reachable from outside the fan-out — the mono/dual topology review-pr and evolve ship, where a
conditionrouter reaches the same reviewer directly or through the fan_out_all — has two distinct predecessors, and the collector election counted both. The target itself was elected: its branch stopped before executing anything, the sibling ran the whole post-fan-out chain inside its branch, and the trunk then ran the target plus the same chain a second time. Observed…sandbox: the claw bind-mount follows the effective backend; a pod that never got placed parks the run failed_resumable (#760) (cdcd63b), closes #724 #699
why
containsClawNoderead each node's declaredbackend:andfallbacks:only. Launch-time model/backend overrides (--backend '*=claw',--model, the studio override object,RunMessage.model_overrides) are applied at dispatch and never folded into the IR, so a workflow ofclaude_codenodes run with--backend '*=claw'got no in-container iterion binary: every node died onexec: /usr/local/bin/iterion: no such file or directory, andsandbox_claw_routed_via_runnerstayed silent.store/mongo: New retries a late server selection; the CI replica-set init fails without a PRIMARY (#754) (d6e7985), closes #729, references #698
why
A fresh client's first server selection can outlast a single 5 s ping on a loaded host while the replica set is healthy — the handshake is late, not the primary absent. New pinged once and turned that lag into a boot failure, and into a conformance harness that ejected a green PR from the merge queue (#729: "rs0 primary elected" printed, then the 5th fresh client saw ReplicaSetNoPrimary and PR #698 was thrown out).
3.104.1 (2026-09-05)
Bug Fixes
webhooks: the GitLab note handler routes every slash command generically — /revi approve and the fork guard reach GitLab (#753) (6f336f4), closes #683
why
GitLab addresses merge requests as a REST resource separate from issues (an MR and an issue can share the same iid in one project), so CommentIssue's /issues/:iid/notes endpoint would land on, or 404 against, the wrong resource for a caller replying on an MR. Adds the MR-scoped counterpart posting to /merge_requests/:iid/notes, needed by the upcoming GitLab /revi approve reply lane.
3.104.0 (2026-09-05)
Features
credentials: a refresh writes only the tokens, refusals earn a rest and stay visible, and every credential's spend is metered by nature (#748) (1906ab1), references #656 #629 #610 #624 #629 #690 #629 #629 #624 #629 #641
why
The three refresh paths did Get -> RefreshRecord -> whole-record Upsert, so a rename committed between the read and the persist was reverted to the label the refresh had read a provider round trip earlier. The rename side was already a store-level $set (SetAccountLabel); this is its mirror.
Bug Fixes
modernize: the contract is not the worker's to rewrite — typed only_lot refusal, done written by the gate, rewrites refused before the gate (#734) (a430ddc), references #737
why
Three defects of one class, measured on a live campaign: a run's terminal state lied about what had been proven, and an operator relaunched on the lie.
3.103.0 (2026-09-05)
Features
review-pr: per-repo review tiers — glance / guard / audit; the pure re-request click is pinned; Revi/Billy/Vetty's shared gate documented (#742) (b2a31a1), references #685 pre-#685 #621 #650 #650 #646 #683 #683
why
Lets a repo's criticality or budget policy pick ONE preset instead of tuning severity_threshold/max_findings/post_to_board/review_mode separately. guard (the default) reproduces the bot's pre-#685 posture byte-for-byte; glance trades depth for a lower floor (cheaper same-family reviewer model via two new topology-routed judge nodes, since model:/ reasoning_effort: only ever resolve ${ENV_VAR}, never a runtime var, on any backend); audit forces the cross-family dual fan-out regardless of…
runner: wire a SubbotRunner into the cloud runner — subbot nodes ran locally only (#743) (1716b9e)
why
The pod's engine was built without WithSubbotRunner (pkg/runner/loop.go), while the CLI and the studio paths carried one. Every
subbotnode on a cloud run therefore died at dispatch withsubbot "x": no SubbotRunner is wired— a net's extension subbot, its re-anchor subbot, and a programme supervisor's per-lot child all existed locally only. "No feature ships local-only", violated by the one surface that runs unattended.
Bug Fixes
claw: carry the run's forfait across the sandbox boundary (it currently cannot authenticate there at all) (#744) (edd5b9d), closes #736 #698, references #698 #687 #698 #736 #687
why
in-process generation — supervisor evals, GenerateObjectDirect — which is what fixed #687's pacer. It did not hold for
backend: clawAGENT nodes on a cloud pod, which are the DEFAULT shape: production runs ITERION_SANDBOX_DEFAULT=auto with an empty override, so those execute sandboxed and the in-container __claw-runner rebuilds its registry from env alone.
3.102.6 (2026-09-05)
Bug Fixes
bots: the plan phase can no longer eat the delivery budget; only_lot on a non-actionable lot fails typed (#737) (b2b3616), references iterion#683
why
Two production runs on iterion#683 spent the whole planning chain (plan -> plan_review -> plan_revise) for 150 min / $8.59 combined and never reached campaign, the node that writes code (native:695). The chain had no ceiling of its own, so it could freely spend the entire run's max_duration / max_cost_usd budget on an optional enrichment (ADR-091) before the actual delivery work ever started.
golden-master: an extension act already present at base is not re-judged (#735) (292e3f6)
why
extension_verdict judged EVERY act in the ledger at HEAD against the run's base. An act introduced before that base has, by construction, its added references in the base tree — so every one of them read as "existed at base — a rewrite wearing an addition's name", and the certifier refused a net that had done nothing wrong.
quota: a reading is trusted for a bounded time, the retry waits for the nearest key, the ceiling counts spenders, the stamp is visible (#730) (f25d224), references #690 #684 #661 #659 #659 #690 #690 #684 #661 #659 #690
why
A reading carrying a reset instant was trusted until that instant, however old. The provider resets windows early: on 2026-09-04 the ledger held 93-99% seven_day readings taken before such a reset, every credential walk skipped both forfaits on them and every claude_code run was refused at admission — the revi/review gate of two PRs with them — for a reset four days out. The lock was self-sustaining: the only writer of a fresh reading is a live session's rate_limit_event, and the refusal is…
3.102.5 (2026-09-05)
Bug Fixes
webhooks: the gate lanes answer, refuse and stay same-repo (#683) (c0a55fc), closes #663 #639 #662, references #642 #642 #646 #663 #650 #646 #650 #650 #662 #662 #663 #662 #662 #662 #650 #652 #662 #662 #663 #650 #650 #662 #663 #663 #662 #662 #662 #662 #662
why
Class surfaced by Revi on #626, fixed there for the review-thread reply lane only. handlePRForgeComment resolves the PR through GetPullRequest → forge.PullRef, then launches with the base repo's CloneURL + the PR head ref — a ref that lives in the head repo on a fork. The checkout misses, or hits a same-named branch on the base and the bot answers grounded in the wrong code, under the bot identity.
3.102.4 (2026-09-05)
Bug Fixes
claw: an Anthropic forfait authenticates claw, on the pod and on disk (#698) (7d8d81c), references #687 #687 #687 #687 #687
why
A run whose only anthropic credential is a Claude Code OAuth forfait resolved an UNAUTHENTICATED claw client: non-nil, so callers proceeded, and every call answered 401 "x-api-key header is required". Revi's pacer supervisor died this way in prod for a full day (#687) while its unit tests stayed green — a clean failure is a feature you believe you shipped.
3.102.3 (2026-09-05)
Bug Fixes
board: the cloud twin claims launches atomically, the reaper gives up on a pruned run, labels adjust relatively, GitHub claims bootstrap their label (#731) (53f5635), references #665 #660 #660 #667 #666
why
The studio's pipeline admission loop launches a Ready card through native.LaunchClaimer — a CAS Ready → in_progress that also reads the claim family, because the dispatcher wins a card with the CLAIM and moves it to in_progress afterwards, off the actor. The Mongo twin never implemented it, so on a cloud board the admission loop degraded to a best-effort SetState: replica A's board dispatcher claims a Ready card (lease live, in_progress move in flight) while replica B's admission tick launches…
engine: a recovery pause retries its node, an orchestration stall recovers in place, and a run read never writes run.json back (#716) (9c3ab3d), references #688 #692 #692 #691 #691
why
The recovery dispatcher parks a FAILED node for a human (AUTH_FAILED, budget, any RecoveryPauseForHuman policy) through the plain human-pause path, with an empty pauseInfo. The checkpoint therefore carried no BackendName, and resumeFromPause has exactly one branch that re-executes the paused node — the delegate pause, keyed on BackendName. A recovery pause fell through to the human path: the acknowledgement became the node's OUTPUT, node_finished was emitted, and the DAG moved on to the gate,…
sandbox/kubernetes: the pod-ready wait is configurable and defaults to 10 min — a 180 s cap killed runs the autoscaler had just made room for (#707) (70da3d9), closes #696, references #694
why
Measured on a 12-node cluster once run pods carry requests (#694): with ten run pods the scheduler answered "0/12 nodes are available: 11 Insufficient cpu" for two minutes, the cluster autoscaler added a worker, the fresh node's CNI took a few seconds, the 736 MB sandbox image pulled in 1m37 — and the driver, which had given up at 180 s, killed the container one second after it started. Cold pulls alone measured 2m25 to 3m05 on other nodes; one run died on that too. Closes #696.
3.102.2 (2026-09-05)
Bug Fixes
launch: the launch path survives one team's broken plugin, strands no row, retries with a budget, commits any message (#709) (ad5405e), references #631 #537 #536 #538
why
A converged run's squash merge failed with "fork/exec /usr/bin/git: argument list too long": the assembled message was passed as one
-margument, and Linux caps a single argv element at 128 KiB (MAX_ARG_STRLEN), so exactly the runs that produced the most could not be merged (#631).
3.102.1 (2026-09-05)
Bug Fixes
webhooks: a merge-queue heal stands down when the queue takes the PR back (#693) (1b1009f), references iterion#682 #692
why
The auto-heal lane launches the brancher bot when the queue ejects a PR, to rebase and push so the PR re-enters the queue. Nothing closed that loop: once the PR WAS back in the queue, the heal kept running, and its delivery tail force-pushes the branch — which cancels the queue build in flight and ejects the PR a second time. The repair becomes the next breakage.
3.102.0 (2026-09-04)
Features
sandbox/kubernetes: run pods carry resource requests and a soft node spread (#694) (8243001)
why
A sibling pod that requests nothing scores every node the same, so the scheduler packs a campaign's runs onto whichever node already holds the sandbox image. Measured on a three-worker pool (8 cores each): five of six run pods on one node at 89% CPU while two workers idled, and a behavioural oracle's 300 s application boot budget blown at 459 s — red verdicts on untouched trees, one run burning its 8 h budget on that red.
3.101.3 (2026-09-04)
Bug Fixes
runner: a stalled resume is adopted, re-budgeted and told apart from a quota pause (#689) (9f7bc53), references #669 #669 #669 #669 #669 #652 #669 652/#669 #652 #669 #652 #652 #652 #652 #652 #669 #669 #669 #669 #669 #669 #669 #669 #669 resume#1 resume#2
why
A DLQ-parked run reached noticeGatePausedForRetry with the same RetryState the pre-park usage-window carried, so the PR got a second "the LLM provider's quota is exhausted … resume automatically at HH:MM" — but nothing wakes a DLQ-parked run automatically; only
iterion remote admin dlqreplays it. Observed live 2026-09-03: the comment sent the operator down the wrong path.
3.101.2 (2026-09-04)
Bug Fixes
credentials: the wants a run actually needs, and a refusal that says why (#682) (d2f7cee), references #627 #668 #654 #668 #668 #659 #659 #668 #668 #659 #627 #654 #659 #668 #654 #627
why
Two paid failures in prod, hours apart, were accepted silently by the server: a claude_code accessToken pasted as a terminal transcript (embedded newlines/ANSI escapes) and a claude_code record missing expiresAt/scopes — the shape the CLI reads as "Not logged in". Both lived on disk after ingestion, so every downstream call from a full fleet of runs would die on 401 for hours before the cause was found.
3.101.1 (2026-09-04)
Bug Fixes
docs-refresh: 3.5.7 — scan_hints stops manufacturing the same false positives every pass (#680) (e1786cc)
why
A link written /dsl is SITE-absolute: every static-site generator routes it from the site root. os.path.join drops everything left of an absolute path, so the scanner asked the filesystem for /dsl and reported it dead. On prod run 01a055f9 that was 20 of the 22 hints, every one false, and the remaining 2 were explicit HTML anchors the heading-only collector could not see.
3.101.0 (2026-09-04)
Features
product-docs: 1.1.0 — platform-agnostic publish tail (image + deploy-target skill) (#675) (ad5d29b)
why
The publish tail spoke one platform: the SSP Cloud datalab (three S3 STS secrets with a 7-day life and a standing serve service), which let the demo URL die by itself and put platform literals in the DSL. It now mirrors app-dev's deploy phase: the bundle's own publish-static-site skill builds the site and packages it with crane as one layer on a non-root nginx base pushed to publish_image:<docs commit> (registry_token on stdin, never argv), and the operator-attached deploy-target skill puts…
3.100.3 (2026-09-04)
Bug Fixes
cloudpublisher: the walk consults the operator's hard caps — a capped forfait is passed over like a refused one (#678) (aa91cb8), closes #677
why
Measured 2026-09-04 on a production tenant: a weekly forfait at 97% utilization (provider still ALLOWING, hard cap 95, reset four days out) was granted by the resolution on four consecutive retry attempts; the runner's pre-flight parked each one in ~1s, and because a park writes no refusal reading, no signal ever engaged the credential-tier fallback — a fresh org-tier forfait at ~0% sat unreached the whole time. Two closures:
3.100.2 (2026-09-04)
Bug Fixes
runner: a re-execution restores the chain its earlier attempt banked (#674) (ea2b274), closes #652, references #652
why
A resume or redelivery of a repo-targeted run re-cloned the target branch and started the remaining nodes on a bare tree: the commits the earlier attempt had banked (final_branch/final_commit) or parked (an attempt ref) stayed reachable on the forge but never reached the workspace, so a campaign that had committed three passes planned its fourth on nothing and the PR tail had nothing to deliver — docs-refresh 01a055f9 (33 commits) and Billy 01a06728 (#652).
3.100.1 (2026-09-04)
Bug Fixes
forge: a re-provision keeps a schedule row's id, vars and last fire; Doki 3.5.6 declares its schedule vars (#673) (7cbf526)
why
syncSchedules rebuilds an integration's schedule rows from the manifests on every re-provision and carried cron, pause and guard tuning over — but not the vars the operator set, nor last_fire_at, and it minted a new id each time. The docs-refresh weekly lost its open_mr/mode twice that way (07-27, 09-03: the second time from a re-provision that enabled another bot), ran a multi-hour full sweep and shipped nothing, while runs and audit entries kept pointing at a schedule id that no longer…
3.100.0 (2026-09-04)
Features
review: Revi cost-reduction pass — severity floor at the source, pacer supervisor, live cost signal, push debounce (#651) (2bd3bbc), references acme/b#7 acme/a#7 acme/b#7 acme/a#7
why
Measured in prod (2026-09-03, 9-run sample): reviewer_claude (opus/high) is 85-93% of a $1.4-$10.5 run (median $3.6), ~28 runs in 3h. The reviewer's judging quality is untouched (stays opus/high); the waste is cut around it:
3.99.1 (2026-09-04)
Bug Fixes
oauth: pre-fill the account name on reconnect, cap the label, and publish the team OAuth routes in the spec (#657) (ed85bf6), references #656
why
Follow-ups from the second review round of #653:
3.99.0 (2026-09-03)
Features
board: claim lease + fenced watchdog for the dispatcher board (ADR-096) (#646) (e194aeb), references #597
why
Commit 0 of the dispatcher-watchdog chantier (C1 slice 2/3) — the prerequisite the plan review made non-negotiable (F3/F25):
3.98.0 (2026-09-03)
Features
oauth: name the account behind a credential, and expose its fingerprint (#653) (5e27183)
why
Nothing on an OAuth record said WHOSE account it was. The payload is sealed, the API view exposed neither a label nor the fingerprint, and the only place the credential is ever identified is a server log line the publisher writes when it picks one (fp=700acc7b…). Answering "whose subscription paid for that run?" therefore meant grepping logs and correlating hex by hand — measured today, with three fingerprints across three owners in one window.
Bug Fixes
mcp: make the explicitly-named wildcard's fatal boot a decision, not an accident (#645) (fd3f89b), closes #638, references #633 #633
why
Two lines apart, expandWildcards hard-fails a wildcard whose MCP server cannot boot and merely warns when that server booted with no tools. The asymmetry is correct — #633 established that ambient servers (target repo .mcp.json, plugin catalog) degrade per-server upstream in buildTask, so a wildcard reaching this function is a DECLARED dependency and must never be dropped in silence — but nothing at the code site said so, and the error named neither the declaration nor a way out. A reader had…
3.97.0 (2026-09-03)
Features
ir: a codex fallback stage is refused at launch where the node will run sandboxed (#648) (c820b54)
why
The codex CLI hard-errors on any non-noop sandbox driver at dispatch — so a codex stage on a sandboxed node fails EXACTLY when the fallback chain is needed, which is worse than not having it. ApplyRunFallback now resolves each node's effective sandbox mode (node override → workflow spec → the deployment's ITERION_SANDBOX_DEFAULT snapshot, threaded through ExecutorSpec from both the cloud runner and the local service) and refuses the stage with the same launch-time voice as the other C176…
3.96.2 (2026-09-03)
Bug Fixes
forge: refresh lead must exceed the tick period, or a token phase-locks onto its own expiry (#649) (1417654)
why
The refresh worker only renews connections expiring within Lead (was 5m), swept every 10m: a 1h installation token could go from "not yet due" to "expired" between two ticks, the refresh then landing AT expiry — and since each mint inherits that phase, the connection locks onto an always-refreshed-at-death cycle. Any run whose launch minute sits just before the lock point is sealed a token with seconds of life: its clone barely makes it, its state push and NATS redeliveries die on it.
3.96.1 (2026-09-03)
Bug Fixes
quota: classify an account spend ceiling, tell the PR when a review parks, stop runs on a closed PR (#639) (cca3d20), references other/#7 widgets/#7
why
"You've hit your org's monthly spend limit · ask your admin to raise it at claude.ai/settings/usage" put THREE words and an apostrophe between "your" and "limit"; the qualifier pattern tolerated exactly one word without one, so the notice sailed through as the node's answer and died as "structured output invalid: missing required field …" — the precise masking bug the pattern's own comment was written for, re-opened by a new wording (three branch-improve-loop runs on 2026-09-03, runs 01a06694 /…
3.96.0 (2026-09-03)
Features
secrets: per-key concurrency ceiling — the operator's answer to fair-usage limits no provider will quantify (#640) (b7e8402)
why
A provider that freezes an account for 'usage pattern' violations publishes NO numeric bound to adapt to: the refusal message names no frequency, the 200s carry no rate headers, and the threshold only reveals itself by tripping it — which is how a whole fleet ended up frozen behind one credential. The bound has to be operator-set.
3.95.0 (2026-09-03)
Features
Revi ticket conformance (Jira & co) + org governance (provision approval, delegated caps) (#630) (cf216f2)
why
Given a tracker_api_base (pinned per repo via the integration's launch_vars) and a bound read-only tracker_token file secret, the reviewers fetch the ticket(s) the PR references — explicit ticket_refs or extracted from the PR title/body + source branch — and verify the diff delivers the demand. Gaps surface as findings of the new "requirements" category (gating like any finding); a per-ticket verdict (covered / partial / not covered / unverifiable) is threaded converge -> pr_gate -> publish…
3.94.3 (2026-09-03)
Bug Fixes
chart: let image.digest pin the server, so one ReplicaSet is one build (#637) (5334b2d), closes #636
why
iterion.imagecould only buildrepo:tag, so "pin the server by digest" was not expressible at all — the operator's only lever was a tag, which is the thing that moves.runner.imageis consumed verbatim and could already carry a digest; the shared server image had no equivalent seam.
3.94.2 (2026-09-03)
Bug Fixes
review-pr: raise max_duration 45m → 90m (#634) (3985c81)
why
The cost cap (max_cost_usd) is the money guard; the duration cap only needs to catch a genuinely hung run. At 45m it killed real reviews mid-publish — a large diff or a usage-window retry walks past it while spending nothing, and the verdict a gate is waiting on dies with the run (observed 2026-09-02, round 7 of the pilot: converge refused at 92% of the duration axis, the review completed everywhere but the publish).
3.94.1 (2026-09-03)
Bug Fixes
mcp: an ambient MCP server that cannot boot costs its tools, never the run (#633) (2eedd4d)
why
The claw splice added every active MCP server — including servers the node never named, inherited from the target repo's .mcp.json or the plugin catalog — as an mcp.<server>.* wildcard, and expandWildcards hard-failed the node when one of them could not boot. One token-less repo server (the repo-scoped sentry on runner pods, which have no SENTRY_ACCESS_TOKEN) therefore killed every claw node of a run at plan_review — observed 3× on 2026-09-02, neutralising the zero-touch fixer lane on this repo…
3.94.0 (2026-09-02)
Features
3.93.0 (2026-09-02)
Features
webhooks: GitHub review-thread conversations — reply to a suggestion, get an in-thread answer (#626) (d4611df)
why
Replying inside one of the bot's review threads on GitHub now launches the converse bot (roleBots().ReviConverse), which answers in the same thread — the GitHub half of the GitLab conversational lane (forge-conversations.md).
3.92.0 (2026-09-02)
Features
runtime: support branch-local bounded loops (#557) (c121be6)
why
The answered branch closed its siblings' resume barrier only through checkpointResumedBranch (the happy path) and the panic recovers. Any other exit between consuming ResumeAnswers and landing the successor cursor — an artifact write failure, an answer satisfying no outgoing edge, a C245/unknown-node guard after an edited source — left the barrier open; under best_effort nothing cancels the siblings and collectBranches only arms its grace timer after a cancellation, so the fan-out hung until…
3.91.0 (2026-09-02)
Features
usagecap: an auth-rejected credential becomes skip evidence — the third refusal family (#624) (b7f2ef0)
why
Re-resolution is already universal server-side (SubmitResume and the retry sweeper both re-resolve), yet a structurally-broken credential kept condemning run after run: it filled its slot on every resolution, gated the pool and platform tiers off, and its failure produced NO evidence for the credential-tier skip to act on. Quota refusals have a family, frequency refusals have a family — the provider rejecting the credential ITSELF had none.
3.90.0 (2026-09-02)
Features
secrets: several keys of one provider become an ordered fallback chain (#612) (7ff4007)
why
Completes the credential chain the fair-usage freeze exposed: the BYOK tier resolved ONE fixed key per provider, so a key whose account the provider froze was resealed into every fresh launch until an operator removed it by hand (measured 2026-09-02: two removals, five cancels, three relaunch waves).
3.89.0 (2026-09-02)
Features
webhooks: an explicitly named review identity lights the re-request lane on GitHub (#605) (9ff26bc), references #604 #608 #608
why
Rebuilt as one commit on main after #604 merged and #608 landed the GitHub/Forgejo replier gate there (convergent fix — both Revi loops demanded it); this is the remaining delta. Four Revi rounds on the PR (3→3→5→0 findings, all real) shaped it; every behaviour guard is mutation-verified.
3.88.0 (2026-09-02)
Features
cli: a command to stand a credential pool up, and a forfait blob that fails where the file name is known (#600) (d87ff60)
why
Two gaps found while actually setting a pool up on a live instance:
Bug Fixes
delegate: a fair-usage refusal parks the run and feeds the credential skip — not the node's answer (#610) (dade12e)
why
Measured 2026-09-02: a provider account under a fair-usage frequency restriction refused EVERY request with a ~330-char relayed 429. The one-liner length cap kept isRateLimitMessage from seeing it, so the refusal text became the agent's output (a campaign node 'finished' with the error as its work_remaining); two modernize lots burned ~7h each overnight and two rites spun full passes on it in minutes.
3.87.0 (2026-09-02)
Features
cloudpublisher: skip a forfait whose window is closed — the credential tiers become a fallback chain (#601) (e963626)
why
The tiers were a fixed first choice, not a chain: a run whose tenant (or the platform) holds an OAuth forfait was never eligible for any later tier, INCLUDING when that forfait's provider window was closed. The run got the exhausted credential, spent one LLM call to be refused, and parked until the window reset - up to a week on the weekly one - while a second forfait or the mutualised pool could have served it immediately.
3.86.1 (2026-09-01)
Bug Fixes
webhooks: the re-request replier gate reaches the prforge lane; an authz error never strands a co-riding resync (#608) (524dabf)
why
TestBudgetGraceCoversDuration slept 350ms against a 570ms graced ceiling, leaving ~220ms for engine overhead — a loaded CI runner spends more than that and the run correctly dies past the ceiling, failing the test (seen ejecting merge-queue entries). Scaled to 2s/2.3s/3.8s: same contract, ~1.5s of slack.
3.86.0 (2026-09-01)
Features
server: the outcome router — a terminal run is decided by its contract, once per episode (#599) (5436277), references #1 #595 #597 #607
why
The measured class, one campaign, 48h: a converged run waited 8h51 for a human; a run marked 'handled' by its external observer was redelivered, worked 6h, converged, and was never looked at again; a third landed while carrying an explicit blocker and reddened every downstream launch. The decision lived outside the authority that knows the state — an external script guessing from a stale copy.
3.85.1 (2026-09-01)
Bug Fixes
routing: the single trusted reading enforces its own preconditions (#607) (d7716eb)
why
Revi's pass on the contract, 5/5 adopted:
3.85.0 (2026-09-01)
Features
webhooks: on-demand re-review via the forge-native re-request button; per-repo merge-gate opt-out (#604) (c5eb318), references iterion#300
why
An operator who pins gate_enabled=false on a repo integration turns the review bot advisory-only — no commit status ever lands — so the statuses-scope derivation that forces re-review-on-sync (whose sole purpose is keeping a REQUIRED check alive across pushes) no longer applies. The pin now disables the forcing on fresh provisions AND releases an already-forced sync through the backfill, in both cases surviving re-provisions — unlike a bare review_on_sync PATCH, which the next provision…
3.84.1 (2026-09-01)
Bug Fixes
server: refuse team-less identities at the auth choke — no more empty-tenant panics on /api/runs* (Sentry ITERION-13/-1W/-1Z) (#606) (097de77)
why
An authenticated identity whose TeamID resolved empty (a PAT minted with no team whose owner has no default team, a GitHub-gated user before any grant) sailed through requireAuth and reached the Mongo store with an EMPTY tenant in ctx — the store's fail-closed guard then panicked on every request: a recovered 500 for the caller, steady-state Sentry noise burying real crashes (ITERION-13/-1W/-1Z, 1800+ events since 2026-08-24 on GET /api/runs, GET /api/runs/{id} and the run WebSocket).
3.84.0 (2026-09-01)
Features
routing: the launch-frozen outcome contract — a run carries what success means (#598) (1253dc4), references #597
why
'converged + nothing blocking' has no generic representation: outputs are an opaque per-bot bag, and a consumer reading only a convergence flag would have auto-landed exactly the blocked run a measured incident came from (a converged branch carrying an explicit blocker — 190 min of downstream work reddened). Only a contract can know the fields.
3.83.1 (2026-09-01)
Bug Fixes
server: a queued run waiting for a free runner is not an orphan (#602) (a6d6fb9)
why
The orphan sweeper reads "queued row, stale, no lease" as "the message is gone" and flips the run to failed_resumable. That is one of TWO causes with the same shape, and the other one is normal operation: a runner pod takes ONE run at a time, so a frozen pool is a hard parallelism ceiling — a campaign of multi-hour runs fills every pod and short runs simply wait their turn, unfetched, lease-less, and stale.
3.83.0 (2026-09-01)
Features
store: numbered episodes with continuation ownership — the run document stops saying nothing (#597) (146c7cc)
why
A run document that says failed_resumable and nothing else forces every outcome consumer to guess: is a redelivery in flight? did it hit its budget wall? was it an operator stop? Measured on a live campaign: a budget wall hidden behind an empty final_error (the code existed — in the run_failed EVENT, never on the document), and an external router that marked a redelivered run 'handled' while it quietly converged.
3.82.1 (2026-09-01)
Bug Fixes
runview: merge is an owned, claimed state machine — no more double-squash between replicas (#595) (b9f8ec4)
why
PerformMergeCtx checked merge_status==merged then went to work: on a multi-replica server (prod runs 3) two concurrent calls both passed the check, both built a squash (different commits under the default squash strategy), and the loser — non-FF push refused — then persisted merge_status=failed over the winner's merged via a full-document ReplaceOne. Found by adversarial plan review, verified in code; the merge path is live on every repo-targeted campaign landing.
3.82.0 (2026-09-01)
Features
store: canonical terminal-state contract + persisted failure taxonomy (ADR-095) (#603) (daa6c8a)
why
One place answers every lifecycle-classification question: policy-named predicates on RunStatus (IsFinalSuccess/IsFinalFailure/IsTerminalResumable/ CanOperatorResume/RequiresResumeAnswers/CanAutoResume/ CountsAgainstLaunchLimit) and the FailureCode vocabulary — runtime's ErrorCode values plus INTERRUPTED/FAIL_NODE/PROCESS_ORPHANED/ QUEUE_SCHEMA_MISMATCH — persisted as an open-world, zero-means-unknown field on Run. Truth-table + relation tests pin every set; the negative-space test forbids new…
3.81.0 (2026-09-01)
Features
reliability: silent-failure pack — effect outbox, terminal cancel, cloud operator alerts, sweep nets (#594) (940cb43)
why
The dispatcher resumed
cancelledruns from their checkpoint (bilan issue-triage friction 7): an operator's cancel was undone on the next tick. The status was in the auto-resume set because the dispatcher's OWN stops (stall reap, external state change, daemon shutdown) used a bare context.CancelFunc, so the engine persistedcancelledfor them too — removing the status alone would have turned stall recovery into a permanent park.
3.80.1 (2026-09-01)
Bug Fixes
queue: survive a queue-backend outage — bounded republish retry, typed 503, budget guard before the sandbox (#593) (e598ebc)
why
A production outage of the queue backend lasted about ten minutes and exposed three measured failure modes:
3.80.0 (2026-09-01)
Features
golden-master: net extension by pure addition — request ledger + the net's own acting subbot (#588) (64eb486)
why
Selftest fixtures and run-note examples used a real target repo's directory name as a path literal; replace with neutral placeholders. No behavior change — harness selftest 70/70, sync test green.
3.79.0 (2026-09-01)
Features
runner: park an unbankable attempt's work on its own ref (#590) (14ea91e)
why
An interrupted delivery, a paused run, and a bankable death on a lease-lost ctx all leave their commits stranded in the git-meta snapshot: the storage branch must not be touched (another pod may own the lease; FinalBranch on a half-done run would be merge-eligible mid-flight), so until now nothing was pushed at all, and turning the snapshot back into a branch takes a manual replay every time — the same measured cost the death bank closed for budget/failure outcomes (nine manual recoveries in…
3.78.1 (2026-08-31)
Bug Fixes
queue: configurable JetStream stream replication (data-HA) (#592) (578f1c1)
why
Production on 2026-08-31 exposed a connection-HA/data-HA gap: a three-node JetStream cluster still held the engine streams and locks bucket at R1. During a broker blip, publishing returned 'nats: no response from stream', in-flight work was interrupted, and resume requests failed.
3.78.0 (2026-08-31)
Features
sandbox: permission policy crosses the claw IPC — gated claw fallbacks under sandbox + feed-watch usage_window fallback (#589) (93878cd)
why
A sandboxed claw node with an enabled permission gate was refused outright: delegate.IOTask carried no policy, so the in-container __claw-runner would have executed bash/file_edit/write_file ungated. That blanket refusal also made a claw fallback route useless for any gated node under the shipped sandbox: auto default — e.g. feed-watch's synthesize, which cannot degrade to the OpenAI forfait when the Anthropic usage window closes.
3.77.2 (2026-08-30)
Bug Fixes
test: deflake the launch tests at their cost, not their timeout (#586) (003d155)
why
TestLaunch_AppliesBudgetOverrides (30s) and TestEngineRunner_SubbotChildHoldsRunLock (60s) both failed on wall-clock this week. Neither timeout is raised here.
3.77.1 (2026-08-30)
Bug Fixes
changelog: backfill the three releases the cutover skipped (#584) (554db02), references #579
why
v3.75.0, v3.75.1 and v3.76.0 shipped while #579 was open, so release-it still ran under
infile: falseand wrote nothing. CHANGELOG.md kept the snapshot taken at v3.74.1 and the v3.77.0 section landed straight on top of it — the only visible trace was a compare link reaching back to a version no section described.
3.77.0 (2026-08-30)
Features
release: commit a CHANGELOG.md fed by release-it, with commit-body context (#579) (501ef49)
why
The conventional-changelog plugin ran with
infile: false, so 264 releases of notes existed only on the GitHub releases page.infilesettles the drift objection that deferred the file: release-it writes the section inbeforeReleaseand stages it withgit add . --update, so it lands in the release commit itself and cannot diverge from the tags.
3.76.0 (2026-08-30)
Features
fallback: run-level fallback becomes an ordered chain — v11 rides beside v10 (#583) (96bc9db)
why
One stage was not enough: a run whose primary AND first fallback share a failing provider still dies, and a three-stage route (subscription → facade → cross-family) was inexpressible. The launch field now accepts a single object (promoted to a one-stage chain, wire-compatible) or an ordered array; stages apply in order, a refused stage is logged and skipped, and the existing fallback event carries the zero-based fallback_index of the stage that took over. Budgets stay per-stage on the existing…
3.75.1 (2026-08-30)
Bug Fixes
bots,docs: Revi follow-up on the plan-phase extension (#578) (#580) (e38c3f8)
why
Findings R0c5ecf R111a3c R62b46b R9c35e4 R80915d R15f1bc R86427d R255dc8 R2c671d R1c4b91.
3.75.0 (2026-08-30)
Features
bots: extend the cross-model plan phase to feature-gap-fill, test-coverage and e2e-coverage (#578) (22a29c0)
why
Replicates the ADR-091 fragment (plan_topology → plan → plan_review → plan_gate → plan_revise; plan_review/plan_review_policy vars; the peer's action:skip fallback route) verbatim from feature-dev, with per-bot mission hand-offs (gap_spec+scope_notes / target+test kinds / matrix_path+target). All three had the exact feature-dev shape (entry: campaign + one continuation_loop back-edge), so the back-edge blanks and the map-every-field discipline carry over unchanged under selected-incoming-edges…
3.74.1 (2026-08-29)
Bug Fixes
gate: the sweep's last pass over a stuck run must be visible at info (#577) (f416cbc), references #564
why
A run that owes a merge-gate verdict and abstains logs its reason at Debug on the sweep path, to keep ~60 identical lines an hour per replica out of the log. Deployments run at info, so those passes emitted nothing at all: the single Warn the event path fires dies with the pod, and a required check left unanswered has no trace anywhere naming why.
3.74.0 (2026-08-29)
Features
queue: the run-level fallback route rides the wire to the pod (#574) (d51fd5d), references #513
why
The launch API accepted
fallback(the operator's single run-level rescue route, ADR-087) and the local executor honoured it — but the cloud publisher dropped it at publish: no RunMessage field, no run-doc stamp, no resume replay. The route meant to rescue a run from a provider's exhausted usage window never fired precisely where runs park unattended (measured on one campaign: 72-85% of two runs' wall-clock lost to usage-window parking a declared fallback would have bypassed).
3.73.0 (2026-08-29)
Features
model,cost,studio: surface model pricing and max-output in ModelCapabilities (ADR-042 follow-through) (#575) (c94ecce)
why
The aggregator has parsed and cached limit.output since ADR-042, but the field stopped at fetchedSpec: nothing downstream could read a model's completion cap. Carry it through merge onto ModelCapabilities, out via ResolvedCapabilities, and into the
iterion modelstable as a MAX OUT column.
3.72.0 (2026-08-29)
Features
feed-watch: deliver a long digest in several messages, never cut (#570) (8bc4e5a)
why
A digest over 14000 chars was truncated with a notice pointing at "the run artifacts" — somewhere its chat readers cannot go. The 28 août Veille Cyber digest (14803 chars) lost its last 803 characters that way.
runner: a dying run banks its branch (#556) (5d2007f)
why
A repo-targeted cloud run that died (budget cap mid-delegate, failure path) left its commits only in the git-meta snapshot: the bank push was gated on runErr == nil, so the branch never reached the forge and the successor restarted from the base commit. Turning the snapshot back into a branch took a manual replay every time — measured: nine manual recoveries in three days of one campaign.
Bug Fixes
feed-watch: size the marker reserve from the marker, and refuse the queue on a truncated digest (#576) (73fdf0c)
why
R79a75e. MARK_RESERVE was 32 while the truncation notice the last capped part carries is 64 chars, so a ceiling-truncated digest overshot the very budget the reserve exists to respect: at limit=1200 the last message measured 1232. An operator who sets a sink's max_chars to the platform's own limit gets that POST rejected — losing precisely the notice saying the digest was cut.
3.71.0 (2026-08-29)
Features
platformcfg: bot vars — ${ITERION_X} resolved from the DB before the pod env (#568) (06627f3)
why
Bots parameterize their model pins, reasoning effort and tunables as ${ITERION_X:-default} expansions, read from the runner pod's env — so re-tuning a bot meant a Helm values change and a rollout, while credentials, bundles, caps, roles and the sandbox image had all moved to the CLI→API→DB settings surface. Same doctrine, fourth family: bot_vars.
3.70.1 (2026-08-29)
Bug Fixes
model: forward host-probed codex version into the sandboxed claw runner (#571) (82f98df)
why
The sandbox image ships no codex binary, so with no ITERION_CODEX_VERSION override the in-container runner fell back to claw's baked-in version string and OpenAI 400'd newer models (gpt-5.6-sol) that the host's own codex install can serve. Forward the host-resolved version through the existing env choke point.
3.70.0 (2026-08-28)
Features
bots: Senti names who is actually vulnerable, not who uses the tech (#545) (59fb4c2), references #veille-vigie-secu
why
The flow that had never run for real now has: two watch-only GitHub Apps (one per org, since a private App only installs on its owner), both on All repositories, both carrying metadata+vulnerability_alerts read and nothing else.
poll_dependabotansweredorgs_ok: 2, orgs_failed: 0— which is the proof that the dependabot_tokens map is keyed by ORG and not by the App's bot handle, the critical the review caught and no test could see.
3.69.1 (2026-08-27)
Bug Fixes
cloud: a resume must keep the budget the launch declared (#554) (72ab85c)
why
SubmitLaunch published the operator's budget override on the wire, but SubmitResume rebuilt its RunMessage with a nil override — so the first usage-window auto-retry silently reverted the run to the workflow's own cap. Measured: a run launched with max_duration 8h, parked by a cap and resumed, died at 14407s/14400s while its doc still displayed 8h. Cloud resumes are unattended, so nothing could re-state the ask.
3.69.0 (2026-08-27)
Features
3.68.5 (2026-08-27)
Bug Fixes
model: forward ITERION_CODEX_VERSION into the sandboxed claw runner (#553) (a2ac17a)
why
The ChatGPT-forfait wire gates model availability on the codex-cli version header. The sandbox image ships no codex binary, so the in-container runner's
codex --versionprobe finds nothing and falls back to claw's baked-in version — which the backend refuses for newer models. Lived on iterion#541's /billy run: plan_review 400'd with "gpt-5.6-sol requires a newer codex-cli" on every cloud attempt, with no operator-side cure since the override never crossed the boundary.
3.68.4 (2026-08-27)
Bug Fixes
bots: plan_review_policy defaults to skip fleet-wide (#548) (f803fe4)
why
Extends branch-improve-loop's default to the three sibling plan-phase campaign bots (app-dev, feature-dev, whole-improve-loop). Two lived incidents the same day made the case: a dead second-family credential blocked a fixer through plan_review auto + wait, and a stale pod OpenAI key paused every cloud campaign the same way. The cross-model peer is an optional enrichment — the primary family alone must always suffice — so a peer failure completes plan_review with the _skipped stamp and the…
3.68.3 (2026-08-27)
Bug Fixes
runtime: build node input from selected incoming edges only (#539) (bd1ca52), closes #484
why
buildNodeInputRS merged with-mappings from every incoming edge whose source had produced output, so a mutually exclusive when/else pair that later converged let the unselected mapping silently overwrite the selected one (Copi's validator verdict never reached the operator).
3.68.2 (2026-08-27)
Bug Fixes
usagecap: the meter follows the credential, not the slot (#541) (57b7062)
why
A usage reading records which WINDOW is spent, keyed by backend and scope (tenant/platform). When the credential behind that slot is rotated — a fresh OAuth token posted over a team's exhausted one — the old account's seven-day reading stayed on the meter, legitimately fresh until its own reset instant, and parked every run of the new credential for days. Lived on a real deployment: a fresh token blocked at 95% by the reading of the account it replaced, resets five days out.
3.68.1 (2026-08-27)
Bug Fixes
supervise: unpinned evaluator prefers the supervised run's own provider family (#546) (1de6be2)
why
An unpinned supervisor resolved its model by host auto-detect, so whatever key sat first in the environment decided — on the prod runner pods a dead platform OPENAI key (429, no credits) failed every Persy eval while the supervised campaign ran fine on Anthropic (run 01a042c2). SpecsFromWorkflow now derives a provider hint from the watched nodes (provider: routing, model prefix, or backend family) and the resolver prefers it when that provider is detected available; pin and…
3.68.0 (2026-08-27)
Features
bots: product-docs publication tail — skill-driven Onyxia deploy behind deterministic gates (#533) (6e8e6ea)
why
publish_gate (opt-in + secrets pre-flight) -> publish agent loading the org-private deploy-onyxia-sspcloud skill explicitly (ADR-059 skills: ref) -> verify_publish (external URL truth gate, fails the run when the site is not serving) -> surface_site_link. French admonition titles in the GitBook->MkDocs converter.
3.67.0 (2026-08-27)
Features
bots: product-docs (Prody) — functional documentation from a multi-repo product catalog (#524) (ff16bf4)
why
New catalog bot generating and maintaining BUSINESS-AUDIENCE product documentation in a dedicated docs repository, grounded in the source code of the N repositories a product catalog names.
3.66.0 (2026-08-27)
Features
platform: DB-backed bot overrides + runtime bot-roles/sandbox settings (#535) (bdafa72)
why
Iterating on a native bot on a cloud instance used to cost an image build + rollout. This makes it one CLI call, mirroring the platform LLM credentials pattern (env/baked = default, DB record = runtime override):
3.65.0 (2026-08-26)
Features
runtime: a bounded grace so a spent run still delivers what it paid for (#532) (96d89ef)
why
A run whose cap runs out mid-way dies holding work it has already paid for, with no way to hand it over: a documentation campaign overran its budget and left a finished, committed corpus with no pull request. The money was spent either way; refusing the last few nodes only decided whether anything came of it.
3.64.2 (2026-08-26)
Bug Fixes
runtime: a budget overrun must not strand the node that earned it (#529) (722b760)
why
A node whose usage takes the run past a hard cap has already SUCCEEDED: its output is validated, persisted and in the store. Failing inside the post-exec check anchored the checkpoint on that node, so a resume with a raised cap re-executed it — for an agent pass, paying its entire cost a second time to reach a result already on disk. Observed on a docs campaign that overran 442/400: the only way forward was to pay for the whole pass again.
studio: default inline subbot frame to live/latest child (#530) (415b981), closes #525
why
The Run canvas picked children[0] (created_at asc = oldest) when the operator had not chosen a tab, so a historical failed child painted the expanded graph red after a later child had already succeeded.
3.64.1 (2026-08-26)
Bug Fixes
ast: serialize supervisor declarations across the cloud queue + Persy dogfood bilan (#528) (129b2a1)
why
The AST JSON codec is the queue's wire format; MarshalFile dropped supervisor decls, so a cloud runner pod recompiled every workflow without its supervisors — no spawn, no skip log. Found live by the first repo-targeted dogfood run on the prod runner (01a03d70).
cli: local file secrets never reached the sandbox (#531) (d9c1c70)
why
A bot declaring a file secret got it mounted nowhere on a local run. The credentials were resolved from the sealed store and stamped into the EXECUTOR's context — but the engine mounts declared file secrets into the sandbox at run start, from the context handed to Run, and that one never carried them. An optional secret was then skipped in silence ("the agent simply won't find the file"), and a required one failed the launch with "has no resolved value" on a host whose store held exactly that…
sandbox: fall back when the engine's own image tag was never published (#526) (ef95dad)
why
A binary built between releases — or from a release whose sandbox image did not ship — pins ghcr.io/socialgouv/iterion-sandbox-slim:<version>, a tag nobody pushed. The pull then fails with a raw 'manifest unknown' and the run dies at startup, before any node: observed on v3.58.3, where neither 3.58.3 nor v3.58.3 exists at the registry, so every local sandboxed run needed a manual --sandbox-default-image.
3.64.0 (2026-08-26)
Features
forge: watch-only GitHub App for org-wide Dependabot alerts (#527) (0a77503)
why
The org-wide alerts endpoint returns only what an installation can see, so covering an org means installing on All repositories. Doing that with the ordinary forge App would grant contents:write — and, when opted in, administration:write — on every repository, as a side effect of wanting to READ alerts.
3.63.0 (2026-08-26)
Features
backends: cool down refused fallback routes (#511) (c123f89), closes #468
why
Keep the typed usage-window cause reachable when a cooled primary is skipped and its fallback also fails. Surface proactive skips in the Studio timeline and clarify the reserved unavailable-reset path.
3.62.1 (2026-08-26)
Bug Fixes
queue: make schema bumps safe across mixed-version runner rollouts (#510) (b8adb7a), references #481
why
A version mismatch on the runs queue was Naked immediately, so a stale runner fleet could burn the whole MaxDeliver budget in seconds during a rolling schema bump — and JetStream then dropped the message silently, leaving the run document queued forever with no recovery path (#481).
3.62.0 (2026-08-26)
Features
dsl,bots: cross-model peer-reviewed plan phase + fallbacks action:skip / when: (ADR-091) (#523) (4f3b7b3)
why
Two new opt-in injections beside review_mode, resolved at launch from the same family set: plan_review (auto -> on iff >=2 distinct credentialed families, family-agnostic) for bots with a peer-reviewed plan phase, and llm_families (the raw sorted family list) so any bot can build its own policy without a new engine role var. InjectAll folds the three into one call + one log summary at the CLI/runview/dispatcher launch surfaces; FamilySet decouples resolution from detect.Report so the cloud…
3.61.0 (2026-08-25)
Features
supervise,bots: Persy perseverance coach + declarative monitors + supervisors kill switch (#522) (7470085)
why
feature-dev v2.2.0 ships the first use of the DSL supervisor block: Persy, a perseverance coach watching the campaign node. Monitors-first policy (give-up markers, Bash failures, budget warnings) with four intervention classes — premature impossibility, expedient path, failure loop, bank under pressure — and an asymptote guard so it composes with the ADR-058 convergence contract. The campaign contract gains the static PERSISTENCE clause. A dedicated test keeps the coach non-vacuous (C190/C193…
3.60.1 (2026-08-25)
Bug Fixes
forge: Revi round 8 (final) — the last three, all consistent with each other (#521) (7efe01a)
why
Revi's gate went GREEN on the previous head (max medium). These are the three it still raised, fixed as the agreed last round.
3.60.0 (2026-08-25)
Features
bots: Senti (vuln-watch) — inventory-scoped vulnerability sentinel, zero LLM (#515) (d6cc7d6)
why
A github_app connection can now opt into SecurityReadEnabled: the refresh worker (and the new PATCH connections endpoint, which mints immediately so a missing grant answers 422 on the spot) mints an org-wide vulnerability_alerts:read installation token and merges it into the team-scoped dependabot_tokens generic secret ({org_login: token} JSON map, egress-pinned to the forge host). The profile is a separate opt-in like DeliveryInstallationPermissions — never folded into the runtime baseline —…
3.59.3 (2026-08-25)
Bug Fixes
modernize: read a scalar exit_gate as one whole command (#520) (7e9dad5)
why
A YAML scalar and a sequence are both legitimate contract forms for a lot's exit_gate. plan_read joined the value with "\n" unconditionally, so a bare string was iterated character by character: the verifier's first command became the single letter of the declared gate, failed with exit 127, and the lot could never converge — a red verdict manufactured by the reader, not earned by the tree.
3.59.2 (2026-08-25)
Bug Fixes
bots: revert a guard built on a false positive, pin the real one (#518) (17df7af), references #508
why
The [high] that motivated
ValidateShellSafeRefin #508 does not exist. Its premise — that a bot'sPUSH_BRANCH={{vars.push_branch}} python3 -creaches the shell unquoted — reads the .bot SOURCE as if it were the final command line. The engine shell-escapes every ref at substitution time (resolveCommandTemplate → shellEscapeValue), so the hostile value arrives asPUSH_BRANCH='x;id;#'. Proven by execution, with the shell itself as the oracle.
3.59.1 (2026-08-25)
Bug Fixes
runner: bank push resolves the LIVE credential through origin (24092c4)
why
The bank pushed with the claim-time token injected into the URL. A GitHub App installation token lives one hour; a paused-and-resumed run banks far later, and the final push — the run's most valuable action — died on a dead credential (loudly, thanks to FinalBranchError) while refreshGitCredentialsLoop had a live token sitting in the clone's credential store the whole time.
3.59.0 (2026-08-24)
Features
- cli: iterion remote admin caps — get/set the runtime usage caps (7f49737)
- runner,runview: usage-cap enforcement reads the live effective policy (f324cd0)
- server: super-admin runtime usage-cap settings API + effective healthz echo (9d4659c)
- usagecap: platform runtime-settings record + TTL-cached live policy resolver (099373f)
3.58.5 (2026-08-24)
Bug Fixes
forge: unbreak the dep-update lane's dead-gate recovery (#508) (6bfe375), references buildkit-operator#21 iterion#504
why
Four production defects found auditing the Renovate auto-upgrade pipeline (buildkit-operator#21 + iterion#504, 2026-08-17/24):
3.58.4 (2026-08-24)
Bug Fixes
cloud: launch model_overrides now reach the runner's executor (427a9f4)
why
A cloud launch's model_overrides were persisted display-only: the studio showed the operator's per-node pins, but the RunMessage never carried them and the runner pod built its executor without them — every delegate ran on the workflow's own models while the Overview claimed otherwise. The local path applies them (service_launch → engine + executor); the cloud path silently didn't.
3.58.3 (2026-08-24)
Bug Fixes
auth: repair remote teams/orgs switch — decode the real /api/auth/me shape (775f9dc)
why
The CLI decoded /api/auth/me with a hand-mirrored struct whose flat
teamsfield the server had re-nested under orgs[].teams: every field silently zeroed, soteams switchrefused every team as 'not a member',orgs switchaccepted only the already-active org,orgs listshowed a single org, andteams listrendered an all-empty table with every row starred (wrong json tags on the same wire).backends: claude_code nodes get only the resolved MCP set (#506) (#507) (fc3f15f)
why
iterion resolves an explicit per-node MCP server set (mcp_server:/mcp: blocks, the repo's .mcp.json via autoload_project, its own ask_user/board servers) and passes it via --mcp-config — but without --strict-mcp-config the claude CLI MERGED the operator's personal user-scope servers (~/.claude.json) on top. Undeclared tools reached the agent, every node visit booted npx/servers/chromium (a CPU spike per iteration on loop-heavy persist bots — the observation behind #506), and personal API keys…
feed-watch: title the digest with its send date, window in the body (3596683), references #452 #452
why
PR #452 titled the WINDOW the queue covers ('Veille Cyber — 21 → 24 août') whenever span_days > 1 — factually honest, but a Monday reader gets a period headline for what is an ordinary digest whose feeds simply published late over the weekend (observed live: gopuyrust '30 juillet → 24 août' after two quota-dead Mondays). Operator arbitrage: the headline names digest_title + the send date ONLY; the anti-breaking spirit of #452 stays as one body clause ('covers the period since <oldest>') when…
pipelines: adopt finished recovery forks on the cloud board (#379) (#502) (d538808)
why
reconcileFinishedTickets — the sweep that adopts a finished fork as the ticket's LastRunID and files it done (cascading waiting_deps dependents) — is gated to local mode, while the board projection that lets the fork replace its dead parent on the card also runs in cloud. In cloud a finished fork therefore took the card (Closed) but the ticket stayed in_progress forever and its dependents parked in waiting_deps.
3.58.2 (2026-08-24)
Bug Fixes
dsl: align {{input.*}} in edge with-mappings with runtime (#500) (cb56808), closes #479
why
Compiler C034 validated edge {{input.x}} against the source node's input schema; the runtime resolved it from the source output overlaid on run-level inputs. Those are not the same namespace, so a mapping could be rejected though it ran, or compile and resolve from a different source than the author/compiler implied (Copi's {{input.reviewer}} worked because --var reviewer=on also landed in run inputs).
3.58.1 (2026-08-23)
Bug Fixes
observability: record the model that actually ran (#474) (#501) (481e857)
why
EffectiveModel was captured on delegate.Result, used for a cost fallback and a log-only drift warning, then dropped before anything durable was written. CLI backends have no llm_request.model escape hatch, so a finished run could not say which model served it.
3.58.0 (2026-08-23)
Features
backends: grok and kimi enforce the permission gate (#498) (a34a071), closes #476, references #497
why
A bot declaring
permission: ask|denycould not use grok or kimi at all — C176 refused every route. The refusal was correct; the coverage was the problem, and it locked out precisely the two backends whose credentials are a CLI forfait rather than a metered API key.
3.57.2 (2026-08-23)
Bug Fixes
dispatcher: a dispatched bot can run its subbots (#499) (4b7ec73)
why
The direct engine path built its engine with ten runtime.With… options and no WithSubbotRunner, so every
subbotnode of a dispatched bot died with "no SubbotRunner is wired". The CLI (pkg/cli/run.go, resume.go) and the studio (pkg/runview/service_launch.go) each wired one; this path never did, and the ADR-046 route that would have borrowed the studio's is inert — WithRunLauncher has no non-test caller, so r.launcher is always nil and ITERION_DISPATCH_VIA_SERVICE cannot switch it on.
3.57.1 (2026-08-23)
Bug Fixes
dsl: refuse an unresolvable tool name before the run starts (C135) (#497) (28f6ebe), closes #478, references #478
why
tools: [read_file, list_files]on a claw node compiled clean. The run started, the worktree was prepared, the sandbox came up — and the first LLM node died onunknown tool "list_files". The name is right there in the .bot: the failure was fully determined by the source, and it cost a launch to discover (found while dogfooding Copi, #478).
3.57.0 (2026-08-23)
Features
runtime: bound the worktree pool — a full checkout per failed run, forever (#477) (dc9a205)
why
A
worktree: autorun parks a FULL checkout of the repository under<store>/worktrees/<run-id>. A clean exit removes it; a failure keeps it, deliberately, for inspection. Nothing ever came back for those:runs pruneonly touches runs/, anditerion cleanis a command you have to know exists. So a store whose runs fail grew by one checkout per failure, with no ceiling and no signal — 355 MB each on this repo, 309 MB of it the vendored tree. A studio left alone for forty minutes reached 32…
Bug Fixes
feed-watch: a push refused for credentials is not a rebase conflict (#457) (a1059c2)
why
This morning's ux-metier digest died on
3.56.4 (2026-08-23)
Bug Fixes
pipelines: a dispatcher give-up reaches Needs attention, not Closed (#495) (95f1460), closes #494
why
When
iterion dispatchexhaustsagent.max_attemptsit files the ticket intoagent.failed_state(defaultblocked) itself — the same terminal state the board's Close writes. The projection read any terminal ticket as "the operator already filed this" and put the card in Closed, so the one class of failure the Needs-attention lane exists for (a pipeline that died and wants a human) was precisely the one it never showed: a deterministic failure burns the whole retry budget on every run.
3.56.3 (2026-08-22)
Bug Fixes
runner: banking verifies the export delivered the pod's final tree (a5c8b87)
why
An export-based sandbox (kubernetes) hands the runner a COPY of the pod workspace. When that copy arrives stale or empty, the host clone reads exactly like a run that made no commits, and bankRepoWorkspace concluded 'nothing to bank' — a silent total loss of a finished run's work (run 01a02a4b: gate converged citing its commit hashes, FinalBranch empty, zero commits recorded).
sandbox: stale host loose refs no longer shadow the exported pod refs (09fb3bb)
why
Adversarial review of the banking invariant found a reproducible false refusal: tar cannot delete, so when a pod-side
git gc/pack-refs --all --prunemoves a ref into packed-refs, the export overlay leaves the host's pre-run LOOSE ref in place — and git resolves loose before packed, so the exported clone reads a pre-run HEAD while every object actually arrived. The guard then refuses work that is sitting right there (and before the guard existed, this exact shape was a SILENT loss — it…
3.56.2 (2026-08-22)
Bug Fixes
runtime: an unknown cost is not a free call (#475) (12dd63d), references #473
why
cost.Annotateomits_cost_usdwhen no price resolves, and its doc is explicit that a zero there means "no cost data", never "this call was free" — callers must not record a $0 sample. The budget was the caller that did:extractUsagecollapsed absent and zero into one float andRecordUsageadded it to costUsed, so a run whose models are unpriced accumulated 0.00 per node.max_cost_usdnever fired and the run finished with no budget event, indistinguishable from one that stayed under…
3.56.1 (2026-08-22)
Bug Fixes
studio: show the resolved model and fallbacks on editor nodes (#472) (0a32460)
why
The canvas replaced every ${VAR} model spec with the word "env", so gpt-5.6-sol / terra / luna were invisible next to the backend chip. Resolve the literal (authored default + /api/resolve-model), shorten the id, and render the authored fallbacks: chain on the card and in the inspector.
3.56.0 (2026-08-21)
Features
arbitrate: Themis — a doctrine-bound judge for blocked divergence cases (379ef20)
why
A modernisation programme stalls each time a lot blocks on a divergence it may not close: the decision (canonicalise the platform noise, re-baseline through the ledger's rite, open a defect lot, or stop) belongs to the contract owner, and until now only a human could hold it. This bot delegates the DERIVABLE part and nothing else: one adversarial judge applies the target repository's WRITTEN arbitration doctrine — refuse by default, every proof read from a committed artifact, anything the…
Bug Fixes
dsl: refuse bounded-iteration edges inside parallel branches (C244) (#471) (55e44f5)
why
fan_out_all, fan_out_each, and llm multi run through execBranch, which has no local loop counters. A declared loop compiled and was then skipped; a foreach with no
whenwas taken as an unguarded unconditional back-edge. C243 rejects those edges. The runtime skip now covers IsBoundedIteration() (loop and foreach) as defence.
3.55.0 (2026-08-21)
Features
merge: repo-targeted runs merge server-side — clone, merge, push to the forge (7787036)
why
A repo-targeted run's workspace is wiped when it returns, so
runs mergehad nothing to stand in: mergeRepoRoot resolved to nothing and every merge died with "no resolvable repo root" — the storage branch the runner banked was unreachable by the product's own merge.
Bug Fixes
golden-master: honour route exclusions written in the refusal's own format (9b2c343)
why
The perimeter refusal prints uncovered routes as "METHOD /pattern" and asks for their exclusion in route-coverage.json — but the lookup only matched bare patterns, so an exclusion transcribed verbatim from the message could never count and the gate refused the same 51 routes it had just dictated. Both key shapes are now honoured: a bare pattern excludes the route for any method, a "METHOD pattern" key excludes exactly what the message named. Self-checks falsify both directions (transcribed form…
3.54.0 (2026-08-21)
Features
clean: reclaim ${PROJECT_SCRATCH_DIR} — the one directory nothing swept (#469) (a72c295)
why
A workspace's out-of-tree scratch had no reaper at all:
runs pruneonly touches runs/, the worktree sweep only worktrees/. One project reached 54 GiB of it — 59 state dirs of 1.1 GiB, none younger than three days, none reachable by any command.
3.53.1 (2026-08-21)
Bug Fixes
- fork: a repo-targeted fork carries its clone coordinates, never a dead pod's path — plus a named 400 for unforkable nodes and a budget hint that names the way out (7c1d577)
- runner: bank a repo-targeted run to the forge — worktree finalization never fires on this path, so a finished run's commits existed nowhere the server could reach (38f1082)
3.53.0 (2026-08-20)
Features
health: the probes echo the usage-cap policy — an unobservable guard cannot be trusted (a984b10)
probes: a lame-duck window, so a deploy stops refusing live connections (#467) (d98af76)
why
On SIGTERM the server went straight to Shutdown. Endpoint removal is asynchronous, so for the 1-10s it takes to propagate, the listener was already closed while traffic was still routed here — a connection-refused: a 502 in the studio, a dropped delivery for a forge webhook. With server.hpa enabled by default that happened on every scale-down, not just on deploys.
sandbox: ship a ready JVM truststore beside the egress CA — six ecosystems read the CA env vars, a JVM reads none (4ad6768)
Bug Fixes
- delegate: the CLI's no-credential render is an auth failure, not an answer (e7a721e)
3.52.0 (2026-08-20)
Features
platform-creds: DB-backed platform LLM credentials — rotate the fallback without a redeploy (#466) (ecea9d5)
why
The credential a tenant-less run inherits — historically the runner pod's env (CLAUDE_CODE_OAUTH_TOKEN from the iterion-forfait k8s secret, ANTHROPIC_API_KEY et al.) — can now live sealed in Mongo, managed by super-admins and rotated with one call that every new launch AND resume picks up (SubmitResume re-resolves credentials). The env stays as the final backstop: an empty platform store keeps today's behaviour byte-identical.
3.51.1 (2026-08-20)
Bug Fixes
bots: golden-master and modernize declare forge_token — repo-targeted cloud launches cloned credential-less (8b31a54)
errtrack: the independent transaction rides a cloned hub (#464) (40eaaeb), references #463 #463
why
Revi's post-merge advisory on #463, verified red-first: sentry.StartSpan installs the new span on the hub's scope (tracing.go:210) and doFinish only restores the previous span for NON-transactions (tracing.go:472) — so StartIndependent on the process-global hub left its finished llm.generate transaction on the global scope forever, and every later captured error/panic inherited that unrelated trace context (reproduced: the regression test failed on the exact trace_id match before the fix). The…
runner: a credentials-inject error fails the attempt, and a credential-less clone failure names its probable cause (3f4a56e)
3.51.0 (2026-08-20)
Features
golden-master: standard 3 — the net states its FEATURES, not only its routes (7b04458)
why
A route the corpus touches once is not a feature it exercises. A net declaring standard 3 must ship a feature_probe (two independent witnesses: the served navigation graph and the tree's own catalogues) and a committed feature-coverage.json mapping every feature to corpus entries or excluding it in writing. The gate refuses unmapped, stale and broken-evidence inventories by name; existing nets keep their declared standard, and the verdict carries the figure so a below-standard net is visible,…
golden-master: the seal opt-in becomes committable — config.json's seal_committed, auditable like the rest (d8ee9ef)
modernize: defect disposition — fixed and recorded by default, preserved only by written business decision (8af1388)
modernize: outcomes — what the programme owes becomes a conjunction term of its convergence (f0e5332)
observability: opt-in Sentry tracing — API transactions + LLM-call spans (Obsy dogfood) (#463) (39ebe8a)
why
Tracing rides the SAME client and DSN as error tracking; sentry-go does not read SENTRY_TRACES_SAMPLE_RATE on its own, so Init resolves it and sets EnableTracing/TracesSampleRate. Unset, 0, unparsable or out of [0,1] ⇒ strictly off even with a DSN — and the refusal is loud, but never costs error tracking. NaN is rejected explicitly: it fails every ordered comparison, so the naive range check would let it through.
Bug Fixes
golden-master,modernize: the adversarial review's findings — a ratchet for the standard, named refusals everywhere, and skills that say exactly what is checked (a6d5b8f)
why
H2: standard-mark makes a silent 3->2 downgrade a named refusal in both drift directions. M2/M3: malformed feature-coverage and mistyped seal opt-ins bail with their cause instead of a traceback or a silence. M1: holdout_awaiting_gate is a report field, not only a notice. H1/C1/B2: the skills now state exactly what the harness checks (union-level source labels), where the outcomes conjunction is enforced today (the campaign runner, not this graph — roadmap named), and that SSR stacks DO expose…
golden-master: a gate never seals a COMMITTED held-out set — it awaits its own gate (26bd79a)
why
The seal moves the set out of the tree, which strips TRACKED files when the set was committed by an earlier authoring run: the finalize then refuses the dirty tree, and the set's single scoring burns on a gate that does not own it. A committed set is now left in place and noted; the convergence gate that owns it opts in explicitly with GM_SEAL_COMMITTED=1 — a flag that can only widen what the gate consumes, never soften a verdict. Selftest 45 -> 48.
golden-master: round-2 adversarial findings — every refusal named, in every type (703cd81)
why
Non-string reasons and feature ids, unreadable standard-marks, mistyped GM_SEAL_COMMITTED spellings, valid-JSON-non-object ledger blocks: each was a traceback or a silence, each is now a named refusal. A feature mapped twice or both mapped and excluded refuses (nobody chose). A mid-path ** in a route pattern refuses (the doctrine says a tail). The standard-raise doctrine now states the PAIRED-commit rule the code enforces. Selftest 57 -> 66.
golden-master: round-3 findings — the refusal envelope survives every path (b99964f)
why
A ledger block that is a dict without a string id escalates as UNPARSEABLE instead of a KeyError two functions later; the seal opt-in refusal reaches the supervisor through bail()'s JSON envelope like every other refusal; a feature excluded twice refuses (two reasons, nobody chose). No false-green found this round. Selftest 66 -> 68.
modernize: the boolean-attribute probe becomes mechanical — rendered DOM, anchors included (4ad6952)
modernize: the judge's INPUTS are immutable too — a corpus can be amputated in silence (fb4a453)
why
Narrowing corpus.json, or widening the coverage exclusions, shrinks what the net measures without moving one reference — and a green cannot report that shrinkage. The immutability check now covers what decides the verdict in all three families: the references compared, the machinery comparing them, and the inputs saying what to measure. Paths are quoted.
modernize: the judge's own machinery is as immutable as its references (9f6ed9a)
why
A lot that rewrites verify-oracle.sh or harness.py makes every later gate answer green without measuring anything, and no reference moves — measured as a real gap. refs_untouched now diffs the runner, the harness and the canonicaliser alongside refs/.
3.50.2 (2026-08-19)
Bug Fixes
bots: the clean-tree ratchet reads the whole clause, and the verb reaches the goal (#462) (62b1d84), references #461
why
Revi's re-review of #461 (on the zero-touch lane's own commits) left two verified findings:
3.50.1 (2026-08-19)
Bug Fixes
fleet: fold Revi's advisory findings into the follow-ups (#461) (2b7abab)
why
The bare
!third_party/codex-agent-sdk-go/.claude/negation re-included the WHOLE subtree, not just the fork's committedrules/: the root-anchored.claude/skills/rule on line 145 does not reach that depth, so…/.claude/skills/JUNK.mdand…/.claude/settings.local.jsoncame back as untracked. That is exactly the runtime junk**/.claude/exists to stop, and the fork is an active work target whose campaign bots commit withgit add -A.
3.50.0 (2026-08-19)
Features
observability: Sentry/GlitchTip error tracking + JSON-default logs (Obsy dogfood) (#459) (e37a117)
why
Adds Logger.SetHook: a callback invoked for every record at or above warn with the record's message and a private copy of its inherited fields. The slot is shared across WithField/WithFields forks like the writer and mutex, so a hook installed on the root logger reaches forks made before or after SetHook.
3.49.0 (2026-08-19)
Features
instrument: Obsy — Sentry/GlitchTip error tracking + log standardization bot (#458) (40c96da)
why
New catalog bot on the proven ADR-058 chassis (feature-dev/Billy shape): ONE adaptive campaign agent + deterministic build/test gate + in-loop adversarial review + bounded continuation loop + opt-in PR tail.
3.48.3 (2026-08-19)
Bug Fixes
dispatcher: a paused human-review run survives a restart (#455) (bdc14ef)
why
After a reboot, sweepStaleLocalClaimsAtBoot frees the claim that parked a card whose last run sits on a human node, and resumableRunID excludes paused_waiting_human by design — so the next tick minted a fresh run from the workflow entry, silently superseding the paused one.
3.48.2 (2026-08-19)
Bug Fixes
feed-watch: a flaky aggregator costs one retry, not a day of veille (#456) (1f71fed)
why
hnrss.org returned 502 for 9 of 69 feeds on a production run and served all of them seconds later. A feed missed is not a feed deferred: nothing re-reads the window nobody fetched, so nine sources went silently absent from that day's digest.
3.48.1 (2026-08-19)
Bug Fixes
byok: a key created for another team is stamped with THAT team (#436) (26dc032)
why
The api-keys store derives tenant_id from the context — on write it stamps the row, on read it filters — and requireAuth stamps the CALLER'S ACTIVE team. The team-scoped routes never re-scoped, so a key created for a team other than the caller's own landed as (scope_team = target, tenant_id = caller's active team): listable from the context that created it, and invisible to the runs of the team it was meant to fund.
3.48.0 (2026-08-19)
Features
devbox: a run that reads a repo need not build it (#450) (34ba661)
why
Two devbox.json files can supply a run's binaries — the bot's own and the target repo's — and until now both installed, always. But "this repo pins a toolchain" and "this run needs that toolchain" are different claims. A review reads a diff and writes comments; it built nothing, and it paid iterion's own 319 Nix paths, 406 MiB downloaded, 1.8 GiB unpacked, a desktop GUI stack included, before its first node. Twice today that cold realise outlasted the window the sandbox had to come up, and the…
feed-watch: a digest that has nothing to say, says so (#454) (2902cac)
why
An empty queue makes a digest exit at plan -> load_pending -> done: no LLM, no post, status finished. Correct behaviour, and indistinguishable from a healthy quiet week — every morning, for as long as it lasts. From 13 to 18 August five daily cyber digests did exactly that while the collector fed nothing, and nobody learned anything until the operator asked.
3.47.2 (2026-08-18)
Bug Fixes
runtime: a run drained before its first node is resumable (#449) (4314aa0)
why
Setup runs on the same ctx the node loop does, so the same two interruptions reach it: a runner drained mid-rollout, and an operator cancelling. The node loop classifies both — handleContextDoneWithCheckpoint writes failed_resumable for a drain and cancelled for an operator — while every pre-execLoop phase wrote a flat, terminal "failed".
3.47.1 (2026-08-18)
Bug Fixes
store: a directory holding only a lock is not a run (#437) (d9a6814)
why
LockRun mkdirs the run directory to place its .lock, so an id that is locked and then never created — an abandoned launch, a crash between the lock and the first write — leaves a directory carrying nothing else. ListRuns reported it as a run, permanently: every LoadRun on it fails, and a consumer that reads the first id it is handed waits on a run that will never load. That is what made TestProcessBoardCardCarriesPRLaunchContext hang for its full 30s while the run it was waiting for sat behind…
3.47.0 (2026-08-18)
Features
feed-watch: a digest dates the window it covers, not today (#452) (1a6eb42)
why
A digest drains a QUEUE, not a day. That distinction is invisible until something interrupts the run: a shut usage window, a paused schedule, a feed that came back. Then the queue holds five days of material and the digest presents it as this morning's news — dated today, ranked as breaking.
3.46.2 (2026-08-18)
Bug Fixes
usagecap: "contains a model node" is not "will call one" (#453) (785288c), references #451
why
The guard shipped in #451 asked whether a workflow CONTAINS something that can call a model. That is not the question a pre-flight needs, and on the very bot it was written for it answers wrong.
3.46.1 (2026-08-18)
Bug Fixes
usagecap: a cap on model spend must not stop a run that spends none (#451) (e88f65e)
why
The pre-flight refused every claimed run while the window was shut, without asking whether the run could draw on it. A workflow made of tool and compute nodes cannot: there is no model call to bill. Refusing it protects nothing.
3.46.0 (2026-08-17)
Features
usage-cap: stop below the provider's wall, at a percentage you choose (#438) (ccf178d)
why
A subscription meters two rolling windows and refuses every call once one is exhausted. iterion already survived that refusal — the run parks and a durable retry resumes it when the window reopens. It could not stop BEFORE the wall, and the wall is rarely where an operator wants to be: the same subscription usually pays for their own interactive work, so a fleet of bots that drives it to 100% takes the human down with it.
3.45.1 (2026-08-17)
Bug Fixes
forge: a GitLab gate claim over an existing claim is claimed, not failed (#435) (7411369)
why
GitLab's commit-status state machine refuses pending -> pending with HTTP 400 ("Cannot transition status via :enqueue from :pending"), where GitHub accepts the same POST as a no-op. The only writer that posts pending is the merge gate's in-flight claim, so the rejection meant the check read "absent" while a review was in fact running -- observed live on a repo where a second bot was invoked on a head another had already claimed.
3.45.0 (2026-08-17)
Features
clean: a run you can resume still owns its worktree (a902f11)
why
Eighth pass. The first whose end-to-end campaign — 18 worktrees, every class, both layouts, concurrent sweeps, re-runs — found no way to lose data. What it found instead was a question asked wrong since the first commit.
clean: the leftover checkouts reclaimed — landing decides, not age (c116029)
why
A
worktree: autorun that succeeds removes its checkout; one that fails or is interrupted leaves it behind for inspection and never comes back for it. On a long-lived store that pool is where the disk goes, andruns prunecannot reach it — its own doc says it never touches worktrees/.golden-master: a converged campaign leaves a mergeable tree (d2bbf23)
why
Two sources of end-of-run dirt, each of which forced a human landing: the harness self-copy now materialises ONE canonical form and writes only on change (no more header churn between the standalone and the inlined node), and emit_runner commits its own emissions — runner, report, harness, a bytecode-cache gitignore — scoped to the oracle dir. A landed-by-hand net is where a hardened runner got clobbered once.
golden-master: a pending re-baseline request blocks the gate (369f6d3)
why
Four requests sat unacted behind four consecutive green convergences: each one quarantines known-diverging entries out of the verdict, so the gate was green AROUND a narrowing net — the failure this bot exists to catch, one level up. Pending requests are now a conjunction term, parsed from the ledger's machine blocks; supersedence becomes the declared 'replaces' field (prose does not count), and an unparseable block is an escalation, never a guess. Selftest 32 -> 38.
Bug Fixes
clean: a bare repo is still a repo, and a photograph is not the present (c6f610f)
why
Third adversarial pass, aimed at what the second one rewrote — which is where it found most of what follows.
clean: a guard buried in
err == nilis a guard that disappears (3404f19)why
Sixth pass, on the fifth's own code.
clean: a repository that answers about itself proves nothing (cf3c94b)
why
Fifth pass. It overturns one of the fourth's fixes, which is the honest outcome: what round 4 removed as a false positive was the only thing standing between a self-contained clone and its own destruction.
clean: git answers in absolute paths, and a store dir need not (ec3f9cb)
why
Seventh pass. Six rounds had been finding regressions of the round before; this one found something that had been there since the first commit and that every guard added since silently depends on.
clean: git must be talking about THIS directory, and merged must mean built upon (2c0050a)
why
An adversarial pass broke the first cut in ways that destroyed work at the default level. Each of these was reproduced before it was fixed.
clean: re-derive the whole verdict before deleting, not just the dirty bit (5de5151)
why
Fourth and last adversarial pass, aimed at what the third one wrote.
clean: take the run's lock, and stop calling "we could not tell" a leftover (9e53b63)
why
Second adversarial pass. The first one's fixes were right and incomplete: what it left open destroyed work, and what it over-refused made the command nearly useless.
golden-master: the emitted runner keeps the log out of the verdict it re-reads (61324fe)
why
The template piped the harness's stderr into the JSON report, so the runner died parsing its own progress messages — a campaign found it, hardened its materialised copy, and the next emission reintroduced the defect from this template. Fixed where it is emitted from.
server: resume dispatcher child gates from persisted source (#433) (4876453)
3.44.0 (2026-08-14)
Features
golden-master: the corpus states its perimeter, and watches what teams skip (75024e2)
why
Five required corpus probes — a creation, the error-then-corrected journey, a case pair, a text sort, a login case variant — enforced mechanically before boot, exactly like the mutant archetypes: a tag without its shape does not count. A routes_probe + route-coverage gate names every route the corpus never touches, unless its exclusion carries a written reason. Write entries gain multi-step journeys in one session. The write surface gains a create_lost archetype. Fixture doctrine: seeded id…
modernize: crossing a major owes a sweep — eight drift classes on the record (dbdc109)
why
A major redefines semantics under unchanged lines: routing edge forms, implicit binding conversion, query strictness, template attribute semantics, dialect functions, seed allocator state, collation, and defaults that flip silently. The upgrade-archetypes skill carries the eight classes as data; each is instantiated from the migration notes of the exact major crossed, probed mechanically, and recorded in the tree. The plan contract gains crosses_major, and the sweep record is checked by the…
Bug Fixes
golden-master: seal the held-out set where every runtime can write (7ef20a1)
why
The sealed pile lived beside the workspace, which assumes a writable parent — a sandboxed run mounts the worktree's parent read-only and the first seal died on it. The default root is now the system temp dir, and the name hashes the absolute workspace path: sibling worktrees of one repo share a basename, and under a common root they would otherwise seal into each other's pile. GM_SEALED_DIR and GM_SCRATCH still override.
pipelines: stop leaking review-scope banners when stepping turns (#427) (b80ed51)
why
ReviewScopePanel and HumanPromptForm shared the same React key as siblings. React's remaining-children map keeps only the last child per key, so Prev/Next unmounted the form and left every previous ReviewScopePanel mounted. A card with dozens of human gates then stacked "No file diff for this review" once per visit.
3.43.0 (2026-08-13)
Features
studio: preview JSON, markdown and text on human gates (#425) (919efc3)
why
Inbound file fields only rendered images, audio and video. A planner outline.json or a review brief therefore collapsed to Download, so the operator answered the gate without seeing the document.
Bug Fixes
3.42.0 (2026-08-13)
Features
ultra11y: an accessibility auditor whose findings no model can drop (#409) (e5ca08f), references maxgfr/ultra11y#15
why
Acci's own bilan records the failure this bot exists to remove. On run 019f3d3b-7aea the RGAA review found four real defects, emitted them without the
statusfield the gates count by, and the report published "0 non conformes" — four true findings erased between the agent and the deliverable. The gates were right. The DETECTOR was a language model, so a dropped field was indistinguishable from a clean repo, and the fix that day (count a status-less finding as NC) is damage control around the…
3.41.1 (2026-08-13)
Bug Fixes
vetty: a verify that overruns is a red build, not a crashed run (#422) (004abce), references iterion#386 #412
why
Observed on iterion#386 today: a 20-minute verify ended the RUN, not the build. TimeoutExpired was caught and then the handler itself raised — e.output is bytes on the timeout path even though text=True was passed, because the decode happens after communicate() returns normally and a timeout attaches the raw buffer instead. bytes + str is a TypeError, so the operator got failed_resumable and a traceback where a verdict belonged.
3.41.0 (2026-08-13)
Features
forge: GitLab reads commit statuses, and a repo launch URL is canonical (#421) (543910d)
why
GitLab's AdminClient gains ListCommitStatuses (the CommitStatusLister capability), the read half of the merge gate: without it the gate reconciler must abstain on GitLab — a review that dies leaves the required context absent forever — and the auto-fix lane can never launch, since it refuses to act on a gate it cannot see. GitLab returns every status row on a commit (retries included), so the result keeps only the newest row per name; a gate reader handed raw history could match a stale verdict…
3.40.6 (2026-08-12)
3.40.5 (2026-08-12)
3.40.4 (2026-08-12)
3.40.3 (2026-08-12)
Bug Fixes
vetty: a hold must say what broke, and a task body is not a command (#412) (c033cdd)
why
Three holds on 2026-08-12 reported "build/tests not green" over an excerpt containing nothing but a list of ok lines and a bare FAIL. The report carried out[-4000:], and a test runner prints its per-package successes after the package that failed — so the blind tail is systematically the wrong excerpt. It now carries the matched failing lines too.
3.40.2 (2026-08-12)
Bug Fixes
expr: concat() is the array builtin — string joins use +, and a guard now says so (8125c06)
why
A compute expression written concat('prefix: ', outputs.x.log) parses, compiles and validates clean, then dies at evaluation with "want array". It lives in notice/fail_log fields inside if(converged, …, <the concat>) — the FAILURE branch — so it crashes only once something else has already gone wrong, turning a reported failure into a dead run. It had shipped in a bundled subbot's refusal path; a sibling bot hit the same shape on its first real run and exposed the class.
review-env: an attached skill is a FILE, not a registry entry (82b8eff)
why
First real run: the plugin mirrored deploy-target.md into the workspace's .claude/skills/, the agent asked the Skill tool for it by name, got "Unknown skill" — that registry carries the bundle's own skills — and correctly refused to deploy. The refusal worked; the premise was wrong. The prompt now names the mirrored PATH as the authority (the pattern app-dev's validated deploy phase already used) and says a missing registry entry is not evidence of a missing skill.
review-env: the cluster is the authority on pullability, not an anonymous probe (e14360c)
why
Two runs refused to deploy a NAMED image because a probe of the private registry answered 401 — a probe the agent was never meant to run: the pull credential lives in the namespace as an operator-provisioned imagePullSecret it cannot read, by design. So it predicted the failure instead of measuring it, and left the only question that matters unanswered. The prompt now says: once you can name the image, apply and let the rollout answer; ImagePullBackOff is a measurement you report.
review-env: the mounted path is the rendered template, not the env var (e18e9bc)
why
Measured on the first deploying run: a secret's env: declaration is injected into TOOL nodes (executor_tool, verified actions), not into a delegated agent's shell — $DEPLOY_CREDENTIAL was unset and the agent had to recover the path from the task header. The prompt now gives {{secrets.deploy_credential}} as the reliable path and says the unset env var is expected, not evidence of a missing credential.
3.40.1 (2026-08-12)
3.40.0 (2026-08-12)
Features
review-env: a live review environment as one leasable capability (1757a67)
why
Deploys the workspace's already-CI-published image to the operator-attached platform and hands back a live https URL — realism for end-to-end tests, captures and review that localhost cannot give. The platform lives ENTIRELY in the attached deploy-target skill (one plugin enabled per instance; swapping infrastructure = swapping the plugin + the deploy_credential secret, never the bot), the credential is used strictly by reference, the image is the repo's own CI's, and the URL verdict is…
Bug Fixes
the last two Vetty holds, and the git-env scrub everywhere it belongs (#407) (bcc0952), references #390 #405 iterion#398 iterion#398
why
Two of the eleven Dependabot PRs from 2026-08-10 were held as "build/tests not green" without a build ever having been established.
3.39.0 (2026-08-11)
Features
campaign: a deterministic supervisor carries a whole programme, lot after lot (e693a02)
why
modernize takes ONE lot per run by design; the programme is a suite of runs, and someone has to be the suite. That someone was a human first: a full modernisation programme was replayed end to end under manual supervision and the interventions were counted — nearly all mechanical, and the acceptance criterion held on every re-record act (observed diff == announced set, every time). This bot mechanises exactly what was mechanical and routes the rest to a human.
golden-master: the ledger speaks a machine-readable protocol — request, act, verdict (6eee2c2)
why
A worker that may not re-record announces; the party that owns the net answers. Three HTML-comment blocks in REBASELINE.md carry the protocol so a supervising process can execute the separation of powers mechanically: a request names its lot, its cause and the EXACT expected paths; an act is written only when the observed diff equals that announcement; a verdict only when the full counter-test replayed green on the committed tree.
Bug Fixes
campaign: bool/json inputs render as JSON literals — name the atoms (1545155)
why
{{input.moved}} arrives in a python script as the token 'false', and any json field can carry true/false/null inside. Found by the bot's second real run: the steward crashed on NameError. One line gives the three JSON atoms their Python names before interpolation.
campaign: the engine's materialised node script is not work in flight (15ab7b0)
why
Every script tool sees its own .iterion-script-*.py in the workspace for the lifetime of the node (executor_tool materialises it there), so the supervisor's clean-tree refusal fired on every run, and the steward would have counted it in every observed diff. Found by the bot's first real run: preflight refused a freshly created worktree. Excluded from all three status reads — it is execution machinery, never part of a verdict.
3.38.1 (2026-08-11)
Bug Fixes
git: the pkg/git suite must not report on its own environment (#405) (40231ab), references #392 #395 #397 #399 #394
why
Four of the eleven Dependabot PRs opened on 2026-08-10 were held on a red build none of them caused: #392, #395, #397 and #399 all failed
TestLogAllowsTabsInUserControlledFieldswithauthor: got "iterion-forge-61934180[bot]".
3.38.0 (2026-08-11)
Features
cloud: carry loop_budget_guard onto the queue and into detached runs (#406) (a6b7494)
why
The run-level override stopped at the launch boundary: a cloud pod re-resolved the guard from the workflow and its own empty environment, so
--loop-budget-guard offon a bot that declares nothing ran guarded anyway, andonagainst a bot declaringoffcould still strand its work at the cap. An operator's explicit choice, quietly re-made elsewhere — the failureauto_memoryclosed at schema v6.
3.37.0 (2026-08-11)
Features
dsl: loop_budget_guard through the full precedence chain (#404) (c9014fd)
why
The affordability guard shipped with a process-env escape hatch only, so turning it off was a deployment-wide decision with no per-run or per-bot say — the one engine dial that did not follow the chain every other one uses.
3.36.4 (2026-08-11)
3.36.3 (2026-08-11)
Bug Fixes
a lost alignment must not merge as a clean bump (#400 post-mortem) (#401) (0e33942)
why
commit_checkdecided between thecommittedandcleanverdicts from the shas alone. An unmoved head has two causes, though, and they are opposite verdicts: the bump genuinely needed no alignment, oralignproduced one that never reached the branch. Both are green under the old wiring, andcleanis what the required check reports.
3.36.2 (2026-08-11)
Bug Fixes
runtime: decline a loop back-edge the budget cannot fund (#402) (a62939a)
why
A loop that banks work as it goes — commits in stride, a published report, a PR opened by a tail node — used to start an iteration it could not pay for, die mid-iteration on BUDGET_EXCEEDED, and leave the tail that would have delivered the work unreached. iterion's own docs-refresh weeklies lost 31 and 29 alignment commits that way, on a clone that died with the pod.
3.36.1 (2026-08-10)
3.36.0 (2026-08-10)
Features
bots: non-blocking teach-back on ambiguous missions (6418393)
why
Switch the feature-dev and whole-improve-loop campaign nodes from interaction: human to interaction: async (ADR-081) and rewrite mission item 5: on an ambiguous mission (or a self-picked axis), the campaign posts a teach-back via ask_user_async — the goal restated in its own words plus the load-bearing assumptions — and KEEPS WORKING under those stated assumptions; answers fold in mid-run via the message queue. The blocking ask_user stays reserved for genuine hard stops, and unanswered…
3.35.3 (2026-08-10)
Bug Fixes
runner: a weekly cap parked four reviews with nothing coming back (#389) (c710817)
why
usageWindowRetryAt documents three evidence sources for "the provider's window is shut": the typed error, a classified runtime code, "and the flattened message is a last resort for a host that has neither — which is not hypothetical, since a runner with no dispatcher wired classifies nothing at all." The third one was never implemented. usageWindowEvidence returned false unless a type or a code survived, so on any host where neither does, the provider's own words — sitting right there in…
3.35.2 (2026-08-10)
3.35.1 (2026-08-10)
Bug Fixes
gate: a review in flight is indistinguishable from one that never ran (#387) (da6afa1), references buildkit-operator#19
why
The merge gate only ever posted a verdict, at the END of a run that takes minutes. For that whole window the required context carries NO status, which a forge renders as "Expected — waiting for status to be reported" — byte-identical to a review that was never launched. Read next to the reviewer's comment on the previous commit, it looks exactly like "the bot commented but the gate never went green". Reported from production on buildkit-operator#19 today.
modernize: a blocked lot was re-attempted on every run, forever (98324c9)
why
Measured on a replay campaign: a lot declared itself blocked after forty-two minutes — the toolchain raise it carried changes observable behaviour, and it said so in a committed report rather than force its gate green. The NEXT run picked the same lot again, redid the same work, and hit the same wall. In a single-run workflow that is harmless, since a human reads the report and decides. In a loop it wedges, and it burns the budget doing it.
3.35.0 (2026-08-10)
Features
golden-master: a write surface, and two archetypes that were required nowhere (3cd5a6e)
why
The net only ever read. A corruption applied when content is STORED — a tag lost, an attribute normalised, an identifier drawn afresh on every save — moved no reference and passed the gate green, so proving it took a script outside the net. That is where such proofs end up when the net cannot write.
golden-master: the report names which mutants could no longer be applied (a994d8c)
why
They were already said, in free text, in the middle of the log. A mutant goes invalid for two very different reasons — it mutates nothing, so it never proved anything; or its anchor vanished under a legitimate change, so it DID prove something and has stopped. Only the second is mechanically repairable, and telling them apart means reading a field rather than matching a phrase.
modernize: repair a mutant the lot invalidated, by delegating to the net's own bot (2e682d2)
why
A modernisation lot is entitled to rename a method or restructure a template. When it does, a mutant that patched the old form stops patching anything. The harness calls it INVALID, correctly, and the surface it probed stops being covered — while NOTHING goes red, because an invalid mutant is excluded from the score and the figure looks no worse. A lot can go green while the net gets narrower, which is the one failure a green cannot report.
modernize: surface which mutants the oracle could no longer apply (383bf9a)
why
A lot may legitimately remove the thing a mutant hooks into — a security major withdraws the matcher idiom one named, a front-end major replaces the configuration block another edited. The patch stops applying, the harness marks the mutant invalid, and an invalid mutant neither scores nor dilutes: it simply stops proving anything, quietly, on whichever lane it covered. The gate can stay green while the counter-test that made that lane worth trusting has gone dark.
Bug Fixes
golden-master: collateral blamed the mutant without ever testing a third cause (9ee97e8)
why
A control entry that differs from its reference was attributed to the mutant, and the message offered exactly two explanations: an under-declared blast radius, or a capture that is not isolated. There is a third, and it is the only one in which the mutant plays no part — THE CONTROL ENTRY DOES NOT REPRODUCE ITSELF. Whichever mutant happened to sample it wears the blame.
golden-master: the two copies of the harness had drifted, and the test said otherwise (c19926e)
why
The harness exists twice: inlined in main.bot's oracle_run node, which is the copy that runs, and as oracle-harness.py, which is the copy a human reads. A test claimed to keep them in sync. It pinned the set of top-level function names and the report fields, on the stated grounds that verbatim comparison was impossible.
runview: reset dropped nodes' execution state on run_rewound (#382) (353c0c2)
why
A rewind invalidates the dropped nodes' checkpoint outputs, but the snapshot the studio renders node colours and infos from is folded from the append-only event log — and none of the three reducers handled the run_rewound event Rewind appends. The pre-rewind node_started / node_finished records kept folding in, so rewound nodes stayed painted with their pre-rewind status, duration and error instead of resetting to never-run.
3.34.2 (2026-08-09)
Bug Fixes
golden-master: a reference carrying a carriage return never reproduced itself (ab9c83c)
why
The harness wrote references without a newline setting and read them back without one either. On Linux the write default translates nothing and lets carriage returns reach the disk; the read default enables universal newlines and turns them into line feeds. From the moment such a reference is recorded it differs from what the capture produces — permanently, with no code having moved.
3.34.1 (2026-08-08)
3.34.0 (2026-08-08)
Features
improve-loops: the ratchet — name it, and ask for it before the report (37644da)
why
The asymptote says why a run stops. Nothing said why the next run does not re-earn what this one banked, though the machinery for it ships everywhere: a gate reading a real exit code, a commit landed per verified unit, a diagnostic that makes a defect class impossible to repeat, a bilan that outlives the run. docs/improvement-ratchet.md gives those parts one name and one image each, and states the divergence from the family of ideas everyone will recognise: continuous improvement is unending,…
Bug Fixes
rewind: scope the workspace restore to what the run recorded changing (#381) (4e29c0a), closes #380, references #380 #380 #380
why
iterion rewindforced the ENTIRE workspace back to the pivot's snapshot. On the default run shape — noworktree: auto— that workspace is the operator's live checkout, so one rewind reverted 38 tracked files and deleted 2 that no node of the run had ever written (#380).
3.33.1 (2026-08-07)
3.33.0 (2026-08-07)
Features
golden-master: a canvas lane, and the browser plumbing it shares (276ee97)
why
A canvas is the one surface nothing else can observe: the served document carries an empty tag, and the DOM stops changing once the image is painted. An accessibility audit says so itself — a datum rendered only as colour or as canvas is not restituted. A chart that stopped drawing entirely would leave every reference in a repository identical to the byte.
Bug Fixes
runtime: keep DSL-fail runs rewindable by preserving the checkpoint (#376) (5f56229), closes #373
why
A run that reaches the DSL fail node was definitively unrecoverable: the fail path wrote status failed without a checkpoint, and the status transition purged the one that existed, so neither resume, rewind, nor cancel could bring the run back to a rewindable state — even though the on-disk state was coherent and the workspace snapshots survived.
runview: let a fork replace its dead parent on the pipeline board (#377) (e4c17c7), closes #374
why
A run launched from a board card and recovered via fork disappeared from its card for good: the card kept showing the dead parent with no way to detach it, while the fork — the operator's actual recovery — ran invisibly. Since fork is the only way to recover a terminal run, recovery came at the price of board tracking.
runview: stop the skip-run and finalize log loops (#378) (a8e3c11), closes #375
why
Run ids whose run.json was gone stayed listed by the store and were reloaded on every UI poll, each producing a WARN line — several lines per second, indefinitely, drowning the instance log (50 MB of noise on an active instance). Same family: the finalize recovery re-warned 'cannot read worktree HEAD' every minute on deleted worktrees.
3.32.0 (2026-08-07)
Features
cross-backend model fallback chain (
fallbacks:) (#365) (38d4f07), references #1why
Discharges the cross-API deferral ADR-004 recorded in its Decision (5) and Alternative #1, and records why the obvious generalisation (flip providerFallbackEligible) is unsafe: a backend swap re-shapes seven delegate.Task fields, three pre-run analyses read a single static backend name, and both the run-level usage-window retry and the credential-pool donor cooldown key on the terminal error's type.
e2e-coverage: Endy — matrix-anchored e2e coverage completion bot (f3de156)
why
ADR-058 v2 shape (one campaign + deterministic gate + bounded continuation), specialized for FEATURE-level e2e completeness: a committed feature×coverage matrix is the inventory, the living todo, the done-oracle and the audit trail. The verify_run gate enforces the matrix contract deterministically — parse, allowed statuses, justified exceptions, and a claims check where every covered-* row must cite a test that resolves in the tree (an orphan claim is a red gate). new_test_code is…
Bug Fixes
cli+e2e: two round-2 test findings — a base64 blind spot and a 210s detection (c0d3080)
why
The secret round-trip claimed 'no plaintext on disk' but grepped only the raw bytes; the sealed field is a []byte, which json writes as base64, so a Seal/Open pass-through mutation (no encryption at all) survived the assertion. It now greps the base64 forms too — measured: the mutation that survived is killed, naming the store file.
e2e-coverage: close eight false-green bypasses in the matrix gate (7dcf452)
why
An adversarial review executed seven distinct ways to make the gate say matrix_ok=true on a matrix that proves nothing, counts less than it claims, or is not even the table the operator reads:
e2e-coverage: inventory the nine surfaces the audit found missing (c5521d5)
why
The matrix claimed completeness while nine operator-observable surfaces had no row at all — an omission is exactly what the inventory promise forbids. Six were already covered and only needed citing (bots install core,
bots templates, plugin lifecycle run, /api/v1/pipeline-board, /api/v1/limits/cost, /api/backends/detect); three are real gaps now visible:iterion serveranditerion runnerCLI boot, and /api/effort-capabilities (reached today only as a readiness probe that asserts nothing…e2e-coverage: repair eleven more mis-citations found by the second audit (9ecb92b)
why
A second adversarial pass sampled 40 fresh rows (70 across both audits) and found the same failure mode as the first: a row cites the mechanics of a helper while the WIRING that invokes it goes untested.
e2e-coverage: repair four façade citations found by the matrix audit (dc7d422)
why
An adversarial audit sampled 30 covered rows and found four whose cited test would pass while the promised feature is broken:
e2e-coverage: round-2 gate hardening — a false POSITIVE and six more bypasses (83e588d)
why
The round-1 hardening was itself reviewed adversarially. The worst finding is a false positive, which in a blocking gate costs as much as a hole: the test-file regex required a slash on BOTH sides of tests/ and spec/, so a ROOT-level tests/ (Rust, pytest), spec/ (RSpec) or tests/ (Jest) was rejected — this gate would have refused the legitimate matrix of most non-Go repos and could never converge there.
e2e-coverage: round-3 — the round-2 hardening had narrowed the gate to Go (2e8d7a7)
why
Round 2 fixed a false positive and introduced two more, in the same place: the gate had quietly narrowed to matrices whose citations look like Go test function names — which is this repo's shape and almost nobody else's.
e2e-coverage: verify.sh must be overwritten and workspace-relative (0af0da7)
why
The scratch dir is per-project, shared across runs: V3's verify_build found V1's script pinned to a dead worktree path. The prompt now mandates overwrite + $PWD-relative commands (the gate already runs the script with the repo root as cwd).
e2e: two live fixtures died on a cd into an unexpanded variable (3c822d1)
why
feat_worktree.bot and feat_compress.bot both opened their tool command with cd "${PROJECT_DIR}". That placeholder is only expanded in a bot var DEFAULT, never inside a command, so the shell got cd "" and the node failed before doing anything — TestLive_Feat_Worktree and TestLive_Feat_Compress could never pass, whatever the feature did.
queue: a message from a newer server is transient, not malformed (ca2ef58)
why
A runner that could not decode a delivery Termed it, whatever the reason. For a malformed payload that is right — no consumer will ever decode it. For a payload from a NEWER server it destroys a run: the queue entry is gone while the run document stays
queuedforever, and the only trace is one line in one pod's log. Nothing surfaces to the operator, who sees a run stuck in "queued" with no explanation.runtime: a structured LLM call now anchors a turn, like a text one (5d162d8)
why
Turn capture lived only in GenerateTextDirect. Every node declaring an output: schema — which in iterion is most of them — runs through GenerateObjectDirect and so anchored NOTHING: no TurnCheckpoint, an empty per-node timeline, and
iterion forkfailing with "turn not found" on a run that had plainly executed. The Fork API and the timeline were effectively blind to the majority of claw nodes.studio: a workflow with no LLM nodes no longer crashes the Launch view (457374d)
why
POST /api/runs/preview-cost answers {"nodes": null} for a workflow with no agent/judge node, and CostPreviewChip dereferenced data.nodes.length — the whole Launch view fell into its error boundary, so a tool+compute-only bot could not be launched from the studio at all. Found by the new studio UI Playwright suite (V4 dogfood, run 019fd6e6); the KNOWN-BUG tripwire test now asserts the positive contract instead.
test: detect nested checkouts by their .git, not by directory name (bbe2dd9)
why
The previous fix hardcoded .claude and .works — one is this harness deal, the other is a purely local convention of one operator. Where someone parks their worktrees and sibling clones is not this repo business.
test: the bot-identity audit walked into the operator nested checkouts (8c42f09)
why
TestEveryExecutorConstructionDecidesTheBotIdentity walks the tree to find executor constructions that skip the bot identity, but its skip list did not cover .claude/worktrees or .works — git worktrees and sibling repos an operator keeps on disk. None of their files are tracked here, and their older copies report as offenders of a rule they predate, so the test passed inside a worktree and failed in the main checkout.
3.31.1 (2026-08-05)
Bug Fixes
runtime: iterion's own scaffolding kept converged runs from landing (597b066)
why
At run start iterion mirrors the bundle's skills into the worktree under
.claude/skills/. Finalize then read those untracked files as "the bot left work uncommitted", banked them as a wip commit — and a wip-banked HEAD is never merged, by design. The result: a run whose gate CONVERGED did not land, and the only thing standing in the way was iterion's own scaffolding.
3.31.0 (2026-08-05)
Features
dsl: add auto_memory: — a per-node MEMORY.md switch, off by default (0283edc)
why
Auto-memory behaved differently on each backend, silently, and no .bot author could control it: claude_code's own default is ON, so every node of every run read and wrote the operator's personal ~/.claude/projects/<cwd>/memory/, while claw and pi had no MEMORY.md at all. On a cloud pod, anything written to a pod-local directory died with the run.
Bug Fixes
runtime: a run launched from a linked worktree described another branch (ca72827)
why
iterion runresolves the repo root up to the MAIN repository — that is where .git lives and where worktrees are registered, and it is correct. It then readHEADthere too, which is not: a linked worktree has its own HEAD and its own branch, and the run silently anchored on whatever the main checkout happened to be on.sandbox: host_state=none dropped the git identity along with the mount (365c048)
why
Under host_state: auto the operator's ~/.gitconfig is bind-mounted, and that mount is what gives an in-sandbox
git commitan author. Turning host state off — the documented setting for multi-tenant and cloud runners — removed the mount and, with it, the identity: every commit-producing bot then dies on "Author identity unknown", for a reason unrelated to what it was asked to do.sandbox: the shipped images declare no locale, so the JVM read ASCII (0223a9e)
why
Measured on the slim image:
LANGandLC_ALLare both empty. That is not a neutral state — it IS the C/POSIX locale, and a JVM derivessun.jnu.encodingfrom it and decodes filenames as ASCII. A build whose resources carry an accented name fails on "Problems opening file input stream", naming a file that is plainly on disk. An agent working in the sandbox has to discover this and prefix its own commands to get past it.
3.30.13 (2026-08-05)
Bug Fixes
dep-update-guard: the drift-gate precheck loops back instead of discarding the run (#370) (722bacf)
why
Twice in one day the aligner's correct Vite 8 migration was thrown away because verify.sh omitted the repo's CI drift gate and the deterministic precheck fired at VERDICT time (rc=3 → hold_unstable, alignment discarded). The omission is an authorship defect, not a red build: the agent scopes 'bump-relevant' and rationalises away repo-wide gates (a studio bump 'cannot drift' the Go openapi — but §1b gates are never scoped out).
3.30.12 (2026-08-05)
Bug Fixes
dep-update-guard: verify.sh mirrors CI's exact strictness (#369) (d514f52), references #19
why
EstimateUSD consults claw's LIVE pricing registry first, so the price tests' expectations depended on whatever the network returned that day — flaky on the host, and reliably wrong inside sandbox pods, where this failure has now twice painted a Vetty verify red on an otherwise CI-green PR (#19's Vite 8 alignment being the second). Pin the tests to the fallback path via CLAW_DISABLE_LIVE_REGISTRY=1 except where the live-cache behaviour is itself under test (seeded explicitly).
golden-master: the dirty-tree notice mangled the first path it named (8314a52)
why
git status --porcelainwritesXY <path>, and X is a space for an unstaged modification. Stripping the whole output before splitting ate that leading space on the first line only, so it shifted by one and lost a character:build.gradlecame out asuild.gradle. Later lines were intact.modernize: refuse when the contract cannot be READ, instead of finishing green (7276c3c)
why
A run completed with Status FINISHED having executed no lot at all. The plan reader could not find yq, emitted nothing_to_do, and the graph routed straight to done. Every failure path in that reader called the same emit(), which exits 0 — so three very different outcomes collapsed into one benign one:
sandbox: make devbox actually work for every bot and every repo (ac609c9)
why
A bundle or a repo could declare devbox packages and get NONE of them, with the run continuing as if they were there. Three causes, each measured on a real run rather than reasoned about, and each fixed where it belongs.
3.30.11 (2026-08-05)
3.30.10 (2026-08-04)
3.30.9 (2026-08-04)
3.30.8 (2026-08-04)
3.30.7 (2026-08-04)
Bug Fixes
runtime: a failing llm half of llm_or_human degrades to the human pause (#367) (5819b04)
why
A human node's llm_or_human half runs through GenerateObjectDirect, which takes provider/model-id and has no backend to infer the provider from; the escalate node carried a bare 'claude-opus-5' default from birth. Every prior run took the clean/committed routes around it, and the FIRST needs_decision bump in production (plugin-react 4→6, 2026-08-04, run 019fcd8e-fe0b) crashed with 'invalid spec' at the exact moment the workflow existed to hand over.
3.30.6 (2026-08-04)
3.30.5 (2026-08-04)
Bug Fixes
forge: keep the issue-lane label allowlist across re-provisions (#363) (2dec4f1)
why
Narrowing which freshly-applied issue label dispatches the implementer (
label_allowlist) was a webhook-config PATCH, and Provision rebuilds that config as a whole literal from the manifests. Any bot-set change — the studio Integrations tab PATCHes bot_ids and nothing else — therefore dropped the narrowing, silently and fail-OPEN: an empty allowlist matches every label, so the repo returned to starting a feature-dev campaign on any label added to any issue.
3.30.4 (2026-08-04)
Bug Fixes
dep-update-guard: the escalate node could never fire — bare model spec crashed the direct generation path (#366) (a78b6a7)
why
A human node's llm_or_human half runs through GenerateObjectDirect, which takes provider/model-id and has no backend to infer the provider from; the escalate node carried a bare 'claude-opus-5' default from birth. Every prior run took the clean/committed routes around it, and the FIRST needs_decision bump in production (plugin-react 4→6, 2026-08-04, run 019fcd8e-fe0b) crashed with 'invalid spec' at the exact moment the workflow existed to hand over.
3.30.3 (2026-08-04)
3.30.2 (2026-08-04)
3.30.1 (2026-08-04)
3.30.0 (2026-08-04)
Features
show a node's file changes in the run console (#352) (b6f1b11), references #351 #349 #349 #351
why
Iterating on a bot's configuration meant relaunching from scratch: edit a prompt, and the only way to test it was to re-pay for every upstream node.
iterion rewindre-anchors an existing run's checkpoint on a node it already executed and invalidates what the replay will regenerate, soiterion resumepicks up from there. Same run id — distinct fromfork, which mints a child for an alternative future and leaves the parent intact.
3.29.0 (2026-08-04)
Features
a review gate shows everything changed since the previous gate (#351) (d9bd4cc), closes #349, references #349 #349 post-#349-squash #349
why
Iterating on a bot's configuration meant relaunching from scratch: edit a prompt, and the only way to test it was to re-pay for every upstream node.
iterion rewindre-anchors an existing run's checkpoint on a node it already executed and invalidates what the replay will regenerate, soiterion resumepicks up from there. Same run id — distinct fromfork, which mints a child for an alternative future and leaves the parent intact.
3.28.1 (2026-08-04)
Bug Fixes
credpool: a donated credential must reach the agent, and be accounted for (#360) (a4926ab)
why
Three defects a live end-to-end run on production surfaced. None was reachable by reading: the chain works right up to the runner pod, and breaks in the layer below it.
runtime,runner: a budget death is terminal-acked, never redelivered (#361) (5f64a87)
why
The engine's per-node budget checks built a bare RuntimeError (code only, no sentinel Cause), so the runner's terminal-ack carve-out — which matches errors.Is(err, ErrBudgetExceeded) — missed it and naked the delivery back to JetStream. Observed live (run 019fcc30-b9be): a 96% duration hard limit at the last node turned into six ~40s resume/refail turns, each re-provisioning a sandbox to instantly re-hit the same spent budget. The branch scheduler's twin checks already wrapped the sentinel;…
3.28.0 (2026-08-04)
Features
iterion-owned workspace versioning (#349) (88fb897)
why
Iterating on a bot's configuration meant relaunching from scratch: edit a prompt, and the only way to test it was to re-pay for every upstream node.
iterion rewindre-anchors an existing run's checkpoint on a node it already executed and invalidates what the replay will regenerate, soiterion resumepicks up from there. Same run id — distinct fromfork, which mints a child for an alternative future and leaves the parent intact.
3.27.2 (2026-08-04)
3.27.1 (2026-08-04)
Bug Fixes
studio: file in-progress pipeline tickets as done when their run finishes (#359) (3f73783)
why
The studio admission loop moved a launched ticket to in_progress and stamped last_run_id, but nothing moved the ticket back out once the run reached a terminal status. The run's status drives the /pipelines column, yet hard blockers only count ticket state == done (native.BlockerSatisfied), so a cleanly finished ticket stranded in in_progress forever and every dependent parked in waiting_deps.
3.27.0 (2026-08-04)
Features
mcp: operator MCP server exposing local + remote iterion (iterion mcp) (0727783)
why
Add the public
iterion mcpcommand: a stdio MCP server any client (Claude Code, desktop, Cursor) registers to drive iterion end to end, with two tool families (41 tools, readOnlyHint annotations):
Bug Fixes
mcp: harden the operator MCP server per adversarial review (7a37113)
why
Address every finding of the opus adversarial pass on 603161446:
server: relaunch claim is per-bot; rune-safe reason truncation (#358) (3080fb2), references #357
why
Two follow-ups from Revi's review of #357 (R41df5e, R60c7c8):
3.26.1 (2026-08-04)
Bug Fixes
server,dep-update-guard: a dead merge-gate run recovers instead of silently blocking its PR (#357) (bc9a99c), references SocialGouv/iterion#354 353/#355
why
A run that owed a merge-gate status and died as failed_resumable was never reconciled — on the theory that it would resume. Only usage-window failures arm a retry; a budget-exceeded or exhausted run sat forever and its PR stayed silently unmergeable behind an absent required check (observed in production 2026-08-03: Vetty run 019fc8e5 on SocialGouv/iterion#354).
3.26.0 (2026-08-04)
Features
credpool: lend keys of any provider + fixes from Revi and a live prod run (#356) (d417368)
why
A pledge now offers a Credential — a (source, ref) pair — instead of an OAuth kind alone:
oauth/claude_codeas before, orapi_key/anthropic,api_key/openai, … for a personal BYOK key of any provider iterion knows.
3.25.0 (2026-08-04)
Features
rewind a run in place to an earlier node (#348) (582e891)
why
Iterating on a bot's configuration meant relaunching from scratch: edit a prompt, and the only way to test it was to re-pay for every upstream node.
iterion rewindre-anchors an existing run's checkpoint on a node it already executed and invalidates what the replay will regenerate, soiterion resumepicks up from there. Same run id — distinct fromfork, which mints a child for an alternative future and leaves the parent intact.
3.24.1 (2026-08-03)
3.24.0 (2026-08-03)
Features
credpool: mutualise contributors' unused LLM subscription quota (#350) (8ad6af0)
why
Developers lend the unused part of their Claude Pro/Max or ChatGPT subscription; a run with no credential of its own draws on it, bounded by ceilings the lender sets and revocable at any moment.
Bug Fixes
delegate: include the recovery formatting pass's CLI cost in annotation (6c6383e)
why
runRecoveryFormatterPass folded its tokens into the running totals but its ResultMessage never reached annotateCost, so the cost annotated after a recovery pass was Pass 1's stale total_cost_usd (or missed the pass entirely under per-invocation accounting). Return the pass's ResultMessage and feed it to the same max-across-messages selection the two-pass path already uses. Found by adversarial review.
delegate: price claude_code cost with the effective model and CLI-reported cost (d304cf3)
why
cost.Annotate received task.Model — the node-declared model, empty on every node that relies on backend auto-detection — so EstimateUSD priced against an unknown model, emitted no _cost_usd, and the whole run reported tokens but no cost (the studio report then shows its 'no cost recorded' placeholder forever; observed on the feed-watch cloud runs, where system/init resolved claude-opus-5 but the node declares no model).
feed-watch: canonicalize item URLs through redirects before synthesis (03467f3)
why
Aggregator feeds (FeedBlitz, FeedBurner) serve tracking URLs whose host is the aggregator's, not the article's. The synthesize agent web_fetches those items, lands on the canonical article and naturally cites its final URL — which the deterministic verify_message gate then rejects as off-item, failing the whole digest (observed live: the java digest run 019fc65e died on two baeldung.com links served as feeds.feedblitz.com items).
forge: re-mint the github_app managed token at every repo-targeted launch (0c14674)
why
EnsureManagedSecret returned the stored managed-secret id without touching its plaintext — a ONE-HOUR GitHub App installation token minted at provision time. On a quiet connection (no provision, no worker rotation) every repo-targeted launch pinned that dead token and failed its clone with 'Invalid username or token' (observed live on prod relaunching the feed-watch java digest; the daily schedules never noticed because they resolve the team's forge_token binding instead). Re-mint at the point…
runner: fire run-outcome side effects only on final delivery dispositions (41c7e6f)
why
A generic engine failure Naks for redelivery, and every failed delivery fired the completion webhook + run.<outcome> event anyway — one 'run failed' notification episode per redelivery (the episode key folds updated_at precisely so a later REAL re-failure notifies again), i.e. up to MaxDeliver pushes for a single deterministic failure within a minute (observed live: feed-watch run 019fc65e re-failed 8 times in 70s on its checkpointed verify input).
studio: render token-only run reports instead of the no-cost placeholder (86f2bfb)
why
A run whose model could not be priced records _tokens without _cost_usd; the Report tab treated that as 'no cost yet' and showed the fills-in-as- nodes-finish placeholder forever, even on finished runs. The report now renders whenever any usage (cost OR tokens) exists: bars and rankings scale by tokens when no cost was recorded, and cost cells show an explicit '—' rather than a fake $0. Buckets tie-break by tokens so a tokens-only report still ranks meaningfully.
studio: show per-bucket cost as unavailable instead of a fake $0.00 (2143f6a)
why
In a mixed run (some nodes priced, some not) the report-level hasCost flag rendered unpriced buckets as "$0.00 / 0%" next to their real token counts, reading as "this node was free". The cost and percent cells now decide per bucket: no recorded cost renders as an explicit unavailable marker. Found by adversarial review.
3.23.2 (2026-08-02)
3.23.1 (2026-08-02)
Bug Fixes
studio: closed pipelines read newest-first (#346) (cb62642)
why
The inventory's sort was one value shared by both tabs, defaulting to priority. But priority is a launch-order key, and a pipeline that already ran will never be launched by it again: on Closed the ranking buried this morning's run under a months-old P9.
3.23.0 (2026-08-02)
Features
runtime: a tool node can hand a file it produced to the run (#347) (fd0d0b8), references iterion#332 #336
why
A gate previews a
filevalue by fetchingGET /api/runs/{id}/attachments/{name}— the descriptor's path is a host or sandbox bind-mount path and is not reachable from a browser (iterion#332, #336). So attachments only ever entered a run from a PERSON: the launch form, afile-typed gate field, the 📎 button.
3.22.3 (2026-08-02)
Bug Fixes
permission: a grant lasted exactly one tool call (#345) (e7a80bf)
why
The pause tells the operator their answer is added "to the allowlist for the rest of this run". It was added to one policy, for one re-invocation, and then dropped.
3.22.2 (2026-08-02)
Bug Fixes
tool: a
jsonfield holding a list broke out of its shell assignment (#341) (4a517a2)why
A schema field declared
jsonreaches shellEscapeValue as []any and falls into the scalar-slice arm, which space-joins individually-quoted elements. In an assignment position that is not one value, it is several words:
3.22.1 (2026-08-01)
3.22.0 (2026-08-01)
Features
studio: a needs-attention lane that reserves its concurrency slot (#344) (b49d74b)
why
A pipeline that died mid-flight used to file itself under Closed and hand its concurrency slot straight to the next queued card. Closed means "this reached its end", and a crash did not — so the failure read as done, and by the time the operator fixed it the slot was gone.
3.21.0 (2026-08-01)
Features
runner: graceful lame-duck drain of in-flight runs on deploy (3ec777f)
why
In cloud mode a rolling deploy SIGTERMs the runner pods. Previously a gracefully-drained in-flight run was checkpointed as
cancelled, which the redelivery reconciliation deliberately drops (anti-resurrection guard) — so the run needed a MANUAL resume and every deploy emitted a false "run cancelled" web-push. Only the crash/timeout paths auto-resumed.
Bug Fixes
drain: close what auto-resume re-opened (3e3c2f0)
why
Making an interruption auto-resume turns a rare, operator-driven recovery into a routine one, so paths that were safe because nobody took them are now taken on every scale-down. Found by an adversarial pass over the rebase.
gate-autofix: the unattended launch reached Mongo with no tenant (b68f8a3)
why
The auto-fix lane stamped the auth identity the admission gate reads and not the store identity every tenant-scoped query asserts on. A bus handler is not an HTTP request and carries neither; the inbound-webhook middleware stamps both and this lane copied half of it.
runner: don't resurrect an operator cancel during a lame-duck drain (f700b5b)
why
Adversarial review found a HIGH regression: the shutdown-vs-operator distinction inferred "shutdown cancel" from parent.Err() (the loop ctx), which only means "a SIGTERM happened" — not "this run's cancel came from shutdown". In the default lame-duck mode the pod holds the run for up to DrainTimeout (8h), during which an operator cancel (via the iterion.cancel.<run_id> subject) would be misclassified as a shutdown interruption, promoted cancelled→failed_resumable, and auto-resumed —…
runtime: route mid-node interruptions through the cause-aware handler (b14e11c)
why
Adversarial review of the cause-threading refactor found the clean handling only fired for the between-node/pre-run window (the top-of-loop select). The COMMON real case — a deploy drains a run WHILE an LLM node is executing — surfaced the cancellation as the node's execErr and routed it through failRunWithCheckpoint, which stringifies the error and loses the ErrRunInterrupted sentinel: the runner then fired a spurious "run failed" notification and stamped finalStatus=failed, even though the…
3.20.0 (2026-08-01)
Features
studio: resizable pipeline card drawer + expandable input/output values (#335) (c871a29)
why
A value box that never puts its content permanently out of reach: short values render whole, long ones collapse to a preview with a "Show all N lines" toggle that expands IN PLACE. Every value carries a copy button, and a JSON value (structured, or a JSON string as bot_args carry them) gets a raw/pretty toggle.
Bug Fixes
runtime: an edge mapping wrapped in literal text lost the literal (#337) (262ff04)
why
resolveMappingnever interpolated. With exactly one reference it returned that reference's value and dropped everything around it; with two or more it returned the raw template, unresolved. Only a mapping whose whole value was a single{{…}}came out right — which is why this survived: that is the shape almost every mapping has.
3.19.0 (2026-08-01)
Features
studio: render a human gate's inbound payload so operators see what they are validating (#336) (79d6110), closes #332, references iterion#332
why
A paused gate could not show the operator WHAT they were validating. The data was already there end to end —
Engine.persistPauseresolves the node's incomingwith {}mappings and stores them asInteraction.Questions, which the run console and the pipeline board both receive — but the form is driven by the node's OUTPUT schema, so the inbound half reached the browser and was dropped on the floor. Authors worked around it by stringifying the plan/diff/verdict intoinstructions:; anyone…
Bug Fixes
e2e: pin the fixture's model so it compiles without a host credential (82371ea)
why
The hand-off publish test passed locally and failed on main. C018 rejects an agent node that names neither
model:norbackend:UNLESS the runtime can detect a credential — so a developer machine with Claude Code OAuth compiles the fixture and CI, which has none, does not.handoff: publishing the fallback let a crashed review shadow a complete one (4bd82c8)
why
Found by an adversarial pass over the publish fix, and caused by it.
handoff: the node a manifest names as a source has to publish something (f066c0c)
why
The live dogfood found the hand-off resolving to nothing, and the first read of the evidence — no artifacts on any cloud run, zero
artifact_writtenevents — pointed at cloud storage. That was wrong, and the correction is the whole fix.launch: a bot launched off the board could not publish anything (5157144)
why
The cloud coordinator launches a card from its BotArgs alone, so two things the webhook tail composes inline never reached it: the forge-publish grant and the repo's launch policy. A board-mode fixer therefore pushed its commits and then had nowhere to post — no verdict, no ledger, no merge-gate status — leaving the repo's required check on the pre-push revision, which blocks the PR on a check that is absent rather than red. Measured side by side with the reviewer on the same PR.
launch: the stale provisioning was winning a re-provisioned repo (cdfedc1)
why
Resolving one repo among several provisionings by lowest id is deterministic and arbitrary. On the e2e repo it picks the row from 2026-07-17, on a personal token, over the GitHub App it was deliberately re-provisioned onto — so the verdict would be posted under the operator's own account, which is the identity whose comments the loop guard refuses.
webhooks: the fixer's push was the one delivery the gate never saw (db2676c)
why
The iterion-bot guard skips a pull request our own loop produced, keyed on the sender. On a merge-gate resync the sender is by construction our own forge bot — a fixer that just pushed onto someone else's PR — so the guard swallowed the delivery the whole gate depends on.
3.18.1 (2026-07-31)
3.18.0 (2026-07-31)
Features
branch-improve-loop: let the operator choose where they sit in the loop (d81c0bf)
why
A reviewer alone already puts the human in the middle: findings land, the human decides which to act on and how. The fixer is the more invasive option, and offering only it would take that arbitration away — you either read findings yourself, or you hand the whole thing over.
branch-improve-loop: speak for the head you pushed, and never green it for free (6398c48), references #322
why
A fixer that pushes moves the head, and the merge-gate status stays on the old one. A required check that is ABSENT is indistinguishable from one still running: the pull request waits for a context that will never arrive, with every other check green and nothing pointing at why. That is the failure #322 was written for, and pushing commits creates it on purpose.
detect: report pi, so the preference variable and the studio can see it (a4be02b)
why
pi was reachable only by naming it on a node. Auto-selection filters on what Detect reports, so ITERION_BACKEND_PREFERENCE=pi was inert, and the studio's backend panel and Settings → Backends could not show it at all — for the one backend whose whole proposition is "you already hold a credential for one of ~36 providers".
gate: a red merge gate can launch the fixer itself, if the repo asks (057be3a)
why
only those. The default stays as it was, and that is the decision rather than caution: a reviewer already leaves the developer in the middle (findings land, they choose what to act on, and a comment hands the work over whenever they want). Turning the hand-over automatic everywhere would take that arbitration from every developer on the repo to save one comment.
handoff: a reviewer and a fixer cooperate without the engine naming either (fc3e342)
why
The run-to-run hand-off was written as a pair of hardcoded identities: a
botID != "branch-improve-loop"guard, a producer pinned to "review-pr", and that bot's three node names (converge,merge_reviews,diff_precheck) as engine constants. So the engine knew which bot reviews, which bot fixes, and the internal graph shape of one of them — the coupling CLAUDE.md forbids, and the reason a second reviewer or a second fixer needed an engine PR rather than a bundle.handoff: the fixer answers the review, finding by finding (cbdad61)
why
The hand-off ran one way. A reviewer handed over N findings with stable ids, and the reply was a prose summary: the PR author was shown N problems and then told "hardened this PR", with no way to see which were answered, which were dismissed, and on what grounds. A later review, given nothing, re-raised whatever the fixer had silently decided against — the oscillating relay ADR-058 removed from the catalog, reconstituted across two bots.
pi: drive the openai-codex provider from the host's Codex login (83cb5e5)
why
pi reaches ~30 providers through an API-key environment variable and
openai-codexthrough none of them: that provider is OAuth-only, its tokens live in pi's own auth.json, and they are minted by an interactive/login. So a host already holding a working Codex credential — the very file iterion reads for claw's ChatGPT-forfait path — could not hand it to pi at all, andbackend: "pi"on a ChatGPT plan was unreachable. Verified two ways before building anything: pi's provider table lists an…pi: keep per-run state out of the target repo's checkout (74b71f0)
why
Five review rounds in a row found holes in the guards protecting <WorkDir>/.iterion/pi — a symlink at a component, at the leaf, at the .gitignore inside it; a relative root that made containment fail open; a pre-seeded .gitignore whose last effective rule re-included our files. Each fix was correct and each left an adjacent hole, because they all patch the same premise: iterion writes a credential and a session transcript into a directory the target repository controls.
reviews: the review hand-off carries the patch it already wrote (208852f)
why
Seeding the fixer with the reviewer's findings exists to save it a round. The digest was dropping the fields that do exactly that.
runtime: the engine reports which skills it owns, so a backend can trust them (b4abbdf)
why
The durable fix for a boundary my last two attempts only pretended to hold. pi is launched with
--no-approveto refuse the target repo's project-local resources, but--skillpaths bypass that gate — so handing pi<workDir>/.claude/skillsloaded whatever the repository itself ships there, as a trusted skill, on every node. For a webhook-launched review or triage bot against an untrusted repo that is attacker-authored prompt text.
Bug Fixes
delegate: the other two writers under <WorkDir>/.iterion refuse a repo symlink too (8183b6b)
why
Revi raised this as an open question rather than a finding: if a repo-committed
.iterionsymlink is in the threat model for the credential, it is in scope for the composed system prompt and the session guard, which MkdirAll through the same path. It is — and the primitive is arguably worse there: a repo choosing where a host file lands, with directories created along the way, out of a path iterion picked.gate: the zero-touch lane had no working brake (b066d3a)
why
A simplify pass and an adversarial pass over the new lane. The two agreed on the same root cause from opposite directions: the launch tail was hand-rolled instead of reusing
launchWebhookTarget, and everything that copy silently dropped was load-bearing.handoff: five ways the gate went green when it should not have (e862915)
why
An adversarial pass over the branch. Two of these would have merged unfixed code through a required check.
handoff: the board lane is where a /command bot actually launches (cb60ac0)
why
Moving the seed into the shared launch tail covered every lane but the one that matters most for a
/commandbot: with a cloud dispatcher active, a board-mode command materialises a card and RETURNS — the card is the launch, and the coordinator launches from BotArgs only. The tail is never reached, so the seed was dropped on exactly the path/billytakes in production, with no error and the bot falling back to its DSL default.pi: --skill must not smuggle the target repo's own skills past --no-approve (a951e6f)
why
A security regression I introduced with the bundle-skill fix, caught by Revi. Widening the gate from
len(task.SkillHints) > 0to "the mirror directory is non-empty" changed WHAT gets handed over, not just how much:<workDir>/.claude/skillsis a checkout of the TARGET repository underworktree: auto, so a repo committing its own.claude/skills/x/SKILL.mdhad that markdown loaded into every pi node as an explicitly-supplied skill.pi: a bundle's skills must reach pi (1dd232c)
why
The --skill flag was gated on
len(task.SkillHints) > 0, which is the wrong signal. SkillHints carries ONLY the DSLskills:field — the skill LIBRARY — while every BUNDLE skill is mirrored into <workDir>/.claude/skills/ without ever touching it. So for a bundle bot the flag was never emitted: pi had zero skill awareness, and an agent whose own prompt ordered "LOAD YOUR SKILLS FIRST" was left hunting for files it had no way to see.pi: a relative workdir disarmed the guard entirely, and the walk covered only the credential (67fd79e)
why
Adversarial round 3, again aimed at round 2's fix. Third time the defect was in the previous round's patch.
pi: absolutise the seed root — a relative --store-dir broke the symlink guard OPEN (56a4116)
why
store.ResolveStoreDir returns a --store-dir override VERBATIM, and
iterion schedulerenders it into cron lines as given, so the seed root can be relative. Three consequences, and the first fails open:pi: containment decides the location for every branch, not just the operator's (1f5f744)
why
Adversarial round 5. One finding, and it is a regression against the parent rather than an unclosed gap.
pi: containment fails CLOSED, the leaf guard is unconditional, and restore 7 deleted tests (0006090)
why
Adversarial review (opus, max effort). Two HIGH findings, both verified by execution, and both regressions this series introduced.
pi: gitignore is last-match-wins, so a
*anywhere proved nothing (1d13476)why
Same fail-open shape as the symlink series, one layer up in the PARSING. piWriteIgnoreGuard short-circuited on any line trimming to
*, but git resolves ignore rules last-match-wins: a repo committing.iterion/pi/.gitignorewith*\n!auth.json(or plainly*\n!*) made the function return "already guarded" having written nothing, leaving the seeded ChatGPT access + refresh token stageable by a campaign agent'sgit add -Aand fast-forwarded onto the operator's branch.pi: guard the directory pi actually writes into, and stop failing a legitimate store symlink (5a39c1e)
why
The previous commit put the symlink refusal in writeSystemPromptFile, which was wrong twice over. It could never be the boundary — that function is SKIPPED for a node with an empty system prompt, and for pi
AppendToNativemeans an agent node with only aprompt:yields exactly that — so.iterion/pias a tracked symlink still redirected the extension bundle and pi's own session transcripts, which carry the node's full conversation. And it was over-broad: it refused a symlinked.iterion,…pi: guard the token wherever it lands in the worktree, not just under sandbox (0581b17)
why
Revi caught an asymmetry I created in the previous commit: I added the ignore-guard verification to the SANDBOXED branch only, while the store branch writes to <StoreDir>/pi with no equivalent check — and this repo's own dogfood instructions prescribe
--store-dir "$PWD/.iterion", which puts the store inside the git repo. So the non-sandboxed path carried the exactgit add -Aexposure the sandboxed one had just been hardened against, and the new refusal made the inconsistency look deliberate.pi: ITERION_PI_BIN is a host path, and a source name is not a set variable (f36e7e1)
why
Revi round 19, both real.
pi: keep the ChatGPT token out of git, and stop refusing nodes we do not fund (7903c78)
why
Two more from Revi, both medium.
pi: make state files reach a copy-based sandbox (040b59e)
why
A driver whose workspace is a COPY of the host's (kubernetes tar-streams it at pod start) never sees a host-side write made afterwards. pi is handed three files BY PATH — the iterion extension, the composed system prompt, the openai-codex credential — and resolves them inside the pod, so all three were missing there. Caught by the first live cloud run of the backend (019fb968): every attempt died on
Extension path does not exist, and the other two would have degraded silently, which is worse.pi: make the zero-skill warning reachable, and stop the guard eating a .gitignore (b4c9e20)
why
The diagnostic that says "pi got no skills" was written and then never wired: piSkillArgs took a logger, both transports passed nil, and the failure it exists to report — the agent hunting for files its own prompt told it to load — stayed as silent as before. Both argv builders now carry the backend's logger, and the warning fires on the case that matters: the engine named skills and none of them resolved.
pi: one --skill per skill, and make detection read what the run reads (b56fbf3)
why
Three from Revi, all consequences of the previous two commits.
pi: StateDir reports WHO can plant, and the pre-flight call is finally tested (a2ada61)
why
Adversarial round 4. Two findings, and both are about the previous round's claims rather than its logic — the helper work held.
pi: the codex bridge refreshed on every node and broke under sandbox (8cf9bab)
why
Six defects from an adversarial review of the bridge. The two that mattered were both mine reasoning backwards from an untested assumption.
pi: the codex bridge worked on
runand not onresume(c5b0e76)why
Two defects a resumed run exposed, neither reachable from
iterion run.pi: the containment fix was killing the credential it protects (2e919f1)
why
Adversarial round 2, aimed at the previous round's FIXES because that is where the last five rounds found the defect. It held.
pi: the ignore guard must not follow a symlink the checkout shipped (a37a99d)
why
The path guards walk TO the seed root; none of them looks INSIDE it. And MkdirAll is a no-op on a
.iterion/pithe checkout pre-populated, so a repo can ship.iterion/pi/.gitignoreas a tracked symlink. Two things follow, and the second is the one that matters:pi: the last two Revi findings, one of them my own argument turned on me (4fcef55)
why
detect's Codex probe was more optimistic than the bridge that consumes the credential. codexChatGPTAvailable hand-rolled a parse accepting
auth_mode: "chatgpt"alone, while piCodexSeed gates on CodexCredentialsView.IsChatGPTMode — which also requires an access token and an account id. So a partially-written or logged-out Codex state made detect report pi available, the preference variable resolved there, and the bridge then stepped aside and left the node to die with "No API key found for…pi: the sibling ignore guard had the same symlink hole, one level up (c83e885)
why
Third instance of one shape, so this shares the CHECK rather than adding a third copy of it. piHideWorkspaceSessionDir used a FOLLOWING os.Stat on <WorkDir>/.iterion/.gitignore, which a repo can ship as a tracked symlink. Both outcomes were bad and the second was silent: a DANGLING link made os.WriteFile create an attacker-chosen host file, and a link to any existing path made the stat succeed, so the function returned as if the workspace were guarded — leaving pi's session transcripts and the…
pi: treat Revi's five findings on the pi follow-ups (d269907)
why
high — the stale-seed sweep deleted a live peer's credential. I added the sweep to fix an adversarial-review finding (a SIGKILL strands tokens) and asserted an invariant that is false: "anything already here when a new node starts is by definition abandoned". The root is SHARED — by every node of a run under sandbox, and off it by every run in the store — while iterion permits parallel branches and the studio runs several pipelines at once. A second openai-codex node therefore swept the…
pi: write our own ignore guard instead of trusting one we did not author (1ee62d1)
why
Revi's high finding, and it is the root cause of the two patches before it: the guard verified
<workDir>/.iterion/.gitignoreno matter where the seed root actually landed. That file is best-effort, is deliberately never overwritten when the repo already tracks one, and says nothing about a root--store-dirput elsewhere under the worktree. So the check could pass while the credential sat somewhere it did not cover.runtime: back the sandbox scratch with a shared, persistent host dir (#330) (9c8e0e2)
why
A sub-bot child runs in its OWN container. ${PROJECT_SCRATCH_DIR} resolved to a container-local /tmp/iterion-scratch, so the file a child wrote there was invisible to the parent that later read the same path. The child reported success, the parent read an empty directory, and the run only failed much later — as "not enough results", far from the cause.
runtime: library skills ride the ownership channel, and the seed root refuses a symlink (8121570)
why
A skill hint is not provenance. One is recorded for every skill the workflow's
skills:field references — INCLUDING one the target repo pre-empted, because the hint describes what the agent will see, not who wrote it. piSkillArgs derived<workspace>/.claude/skills/<name>from that name, so an untrusted checkout that shipped a same-named skill got its own file handed to pi as a trusted--skill: precisely the routing-around---no-approvethe gate exists to close.runtime: own the FILE, not the directory a repo can pre-populate (cbee1e1)
why
A flat bundle source writes <stem>/SKILL.md, and MkdirAll succeeds happily on a <stem>/ the checkout already ships. Reporting that DIRECTORY as owned therefore vouched for whatever the target repo planted beside our file: a repo committing .claude/skills/whats-next/evil.md (no SKILL.md, so nothing shadows) got the whole directory back on the owned list. Naming the one file we wrote cannot carry a sibling. Library skills had the identical shape and get the same treatment; the directory-form…
runtime: plugin skills reach pi, and directory skills survive a resume (65227b0)
why
Two regressions the engine-owned skill list introduced, both silent.
secrets: state only the billing arrangement that was measured (2fdcb80)
why
The shared subscription-OAuth warning asserted that "third-party apps bill against your EXTRA USAGE balance, not your plan limits" for ANY provider, interpolating only the name. That sentence describes Anthropic's arrangement, measured live during the pi work. Applied to OpenAI it states a billing model nobody here verified — and an operator reading a confident sentence acts on it.
3.17.7 (2026-07-31)
Bug Fixes
cli: anchor the run store on the working directory (#328) (61af254)
why
iterion run project/bots/x/main.botresolved its store from the .bot's own directory, so a bot living inside the project it drives was keyed on that subdirectory: the run landed in ~/.iterion/projects/<project-bots-x-key>/ while resume, inspect, issue, dispatch and the studio all resolve <project>/.iterion. Launching succeeded and every follow-up reported "run not found" — the run was invisible to the board and could not be resumed.
3.17.6 (2026-07-31)
3.17.5 (2026-07-31)
3.17.4 (2026-07-30)
Bug Fixes
golden-master: five defects the a11y lane only revealed on a real runner (6fbf1ba)
why
The lane worked on the machine that wrote it and failed six times in a row on a CI runner. Every fix below is now verified by a green pipeline — full conjunction, 19 of 19 mutants, on a real GitLab runner with the lane active.
3.17.3 (2026-07-30)
Bug Fixes
golden-master: the browser stalls in a container instead of failing (f29dd4e)
why
/dev/shm defaults to 64 MB in a container, and the renderer blocks there rather than erroring. The symptom is not a crash but a page that never finishes loading: the load event does not come, and the load ceiling fires while blaming the host's speed. Measured on a real runner — 240 seconds on the first page, instant on the same page locally.
server+studio: show the paused node's instructions on review cards (#326) (a46527d)
why
A board review card rendered an answer box with no question above it. The operator saw "Awaiting input", a run link and an empty "Message" field — nothing else.
3.17.2 (2026-07-30)
Bug Fixes
server: a review that dies must still leave a verdict on the PR (#322) (d337007), references #314 #318 #314
why
A required check that is ABSENT is indistinguishable from one still running. The PR waits for a context that will never arrive, and nothing — not the run, not the PR, not the check — says why. Only someone who knows to re-trigger the bot can unstick it.
3.17.1 (2026-07-30)
Bug Fixes
review-pr: a mono review must not report cross-family confirmation (#320) (34bd008), references socialgouv/buildkit-operator#6
why
Every comment ended with "0 finding(s) cross-confirmed by both model families", including under the default mono topology where a single family reviews. That reads as two families having looked and agreed on nothing — a statement about a comparison that never took place. Spotted on socialgouv/buildkit-operator#6.
3.17.0 (2026-07-30)
Features
golden-master: an
a11ylane that audits the rendered page, not the markup (c354a8a)why
A net that watches HTTP responses sees the markup change; it cannot say what degraded. Removing an accessible name moves an HTML reference and nothing in the report distinguishes it from a reworded label.
golden-master: put the target's own test suite through the same trials (7ab1596)
why
The net and the existing test suite both watch the same repository and both claim to protect against regression. Comparing them by assertion is worthless;
suite-vs-net.pyapplies every mutant in turn and runs the target's own suite, so one set of trials produces both figures.
3.16.1 (2026-07-30)
Bug Fixes
dep-update-guard: don't let the merge check claim an alignment that never happened (#317) (4476135), references socialgouv/buildkit-operator#15
why
Observed on socialgouv/buildkit-operator#15, the first PR to travel the whole loop unattended: the required check displayed "supply-chain audit clean; alignment committed, build verified" while the commit step had reported committed=false — the branch needed no alignment at all.
review-pr: the stale-anchor guard compared a template string to a sha (#318) (7b87b5f)
why
Revi has published nothing since the runner picked up #290's reviewed-SHA guard. Every review ends:
3.16.0 (2026-07-29)
Features
golden-master: an
assetlane that inventories the build, not the worktree (0abe16a)why
A net that watches HTTP responses and rendered documents does not watch the files a page loads. On the repository this bot was exercised against, the whole client layer — every stylesheet, every vendor script, the view framework itself — was absent from the environment and answered 404, and not one reference moved. A total absence of the client layer was indistinguishable from its presence.
Bug Fixes
runtime: a subbot child carries its parent from its first write (3a61d2d)
why
The engine created every run with CreateRun and stamped ParentRunID in a follow-up SaveRun. Between the two writes the row existed, was
running, and had no parent — and a row with no parent is indistinguishable from a top-level run, which is exactly what the orphan reconciler judges. Every subbot child goes through this path: the runtime spawns them, and only Service.Launch pre-creates its own rows.
3.15.0 (2026-07-29)
Features
pi: pi as a first-class execution backend (ADR-085) (#308) (0710335), references #168
why
pi (pi.dev) is a multi-provider agent harness reaching ~36 first-class providers behind one agent loop. It is the backend to reach for when a node needs a model claude_code and claw cannot run.
3.14.0 (2026-07-29)
Features
dsl+studio: file schema fields — operator uploads at a human gate (#315) (a6edd19)
why
Adds a
fileschema field type so a human node can ask the operator for bytes, not just text: the studio renders a file picker at the gate, the answer is uploaded to the run, and the runtime promotes it to a run attachment before the workflow resumes.iterion resume --answer key=@./pathaccepts the same fields from the CLI.
Bug Fixes
dep-update-guard: merge the PR the forge already reports as green (#314) (a140746), references socialgouv/buildkit-operator#5
why
enablePullRequestAutoMerge only accepts a PR with something left to wait for; GitHub answers UNPROCESSABLE "Pull request is in clean status" otherwise. The audit takes longer than CI, so that is the ordinary case: a live run on socialgouv/buildkit-operator#5 posted its gate green and then armed nothing, leaving the PR open with every check passing.
golden-master: a gate on an uncommitted tree judges a tree that never existed (0f99ce5)
why
Mutant reverts are
git checkout -- <file>, restoring HEAD. The gate captures references from the working tree it starts with, the first file mutant snaps those files back to HEAD, and every capture after that describes something else. Uncommitted work is destroyed on the way, silently, and the verdict belongs to no tree that ever existed.pluginsource: publish a plugin checkout atomically, one clone per key (#313) (9065d52)
why
git initcreates .git before the fetch and checkout land, and Fetch treated the presence of .git as "this tree is complete". On a cold pod taking several launches at once, the losers of that race were handed a directory holding nothing but .git — and the plugin loader then reported it as "has no plugin.yaml and no skills/", a 502 that names the wrong cause and blocks every launch for the tenant.
3.13.0 (2026-07-29)
Features
projected improvements from the AIDD framework (skill lint, fit/rot lens, memory supersede, dependency gating, reviewed-SHA guard, hold-labels) (#290) (eb0ecbe), references #13
why
ScanFrontmatter (the shared SKILL.md parser used by both the skill library and runview's bundle-skill catalog) only read the value on the same line as
description:. For the commondescription: >/description: |block-scalar form it therefore returned just ">" or "|" — so the router (Nexie) and discovery saw a one-character description for every skill authored that way.
Bug Fixes
bots: mono must not hand the merger a raw template (74c46e2)
why
In mono only one reviewer runs, and an {{outputs.<absent node>.<field>}} reference renders as a LITERAL placeholder rather than as nothing — so the merging agent was shown what looks like a broken template instead of "that family did not run". Observed live: a mono review's own reviewer mistook it for a reviewer failure and said so in its report.
golden-master: the emitted runner names a missing interpreter instead of blaming its own tests (446f9bf)
why
A missing python3 surfaced as "the canonicaliser tests FAIL" — a message that accuses the net when the environment is what is absent. Seen for real on a CI image nobody had checked carried an interpreter. The whole net is Python; saying so plainly costs one line and saves an hour of looking in the wrong place.
golden-master: the replayability check no longer reports success when it cannot tell (fd7bd99)
why
git check-ignorereturns 0 for ignored, 1 for not, and 128 when there is no repository or no git. The check only looked at 0, so on any workspace that is not a checkout it discriminated NOTHING and leftrunner_replayable: true— the good outcome, reported for the one reason it could not see. Some CI runners hand the job a COPY of the tracked files rather than a checkout, which is exactly where this happens.
3.12.1 (2026-07-28)
3.12.0 (2026-07-28)
Features
cli: add --commit flag to version command (#52) (a8af8ee)
why
iterion version --commitprints only the bare git commit SHA on a single line (via cli.RawCommit()), so scripts can capture the SHA directly without parsing the full human-readable version string. The defaultiterion versionoutput is unchanged. Output now goes through cmd.OutOrStdout() so the command is testable against a captured buffer.
3.11.1 (2026-07-28)
3.11.0 (2026-07-28)
Features
studio+runtime: pipeline-board & human-review overhaul + subbot editor UX (WIP snapshot) (#300) (991ee1d), references #244
why
Answering a paused
humannode from the studio (pipeline-board card or run console) could silently do nothing and lose the reviewer's notes. Verified end-to-end in a real browser (Playwright against the built SPA).
Bug Fixes
golden-master: promote_audit commits what it promotes (ae26833)
why
Moving files into the worktree is not publishing them. The worktree is destroyed when the run ends, so a promotion that only moves has produced nothing that outlives the run -- while reporting promoted: 8.
reviewtopology: make mono the default review topology (03309fd)
why
Revi ran BOTH family reviewers on every review, unconditionally: it never declared the ADR-052 topology vars (its only review_mode-looking var is pr_review_mode, an unrelated inline/summary publish setting), so InjectIfDeclared no-op'd on it and there was no frugal path at all. With the merge gate wired, review_on_sync re-reviews on every push — so each push cost two full reviewer passes on an instance that should be running mono.
3.10.4 (2026-07-28)
Bug Fixes
dep-update-guard: a redirect must not degrade the publish POST into an unexplainable 401 (#312) (9d5efc6)
why
forge_publish_urlis the FULL endpoint URL, not a base — Revi uses it verbatim. Vetty appended the path to it, so it POSTed to…/api/v1/forge/publish-review/api/v1/forge/publish-review. That path is not the auth-exempt route, so the global auth middleware answered 401 "authentication required" and no commit status was ever posted.
3.10.3 (2026-07-28)
3.10.2 (2026-07-28)
3.10.1 (2026-07-28)
Bug Fixes
3.10.0 (2026-07-28)
Features
webhooks: /revi approve — maintainer override for the merge gate (#292) (737d2fe)
why
The human-arbitration escape hatch for the Revi merge gate: a trusted maintainer comments
/revi approve [reason]on a PR to force-green therevi/reviewcommit status on the current head, for a finding they dispute — without launching a re-review and without needing admin merge-queue bypass.
Bug Fixes
dep-update-guard: publish to the endpoint the server injected, not under it (#309) (1867b14)
why
forge_publish_urlis the FULL endpoint URL, not a base — Revi uses it verbatim. Vetty appended the path to it, so it POSTed to…/api/v1/forge/publish-review/api/v1/forge/publish-review. That path is not the auth-exempt route, so the global auth middleware answered 401 "authentication required" and no commit status was ever posted.
3.9.1 (2026-07-28)
Bug Fixes
golden-master: one rule for the sealed path, asked rather than copied (b8ab790)
why
The morning's fix made the campaign and the gate DERIVE the sealed path from the same rule instead of one dictating it to the other. The very next commit added a third party -- the node promoting a spent set to published evidence -- carrying its own hand-written copy of that derivation. It resolved into a different repository's scratch entirely, promoted nothing, and reported success.
runview+cli: a paused run advertised resumable before it was, and the group guard demanded a contract three groups already had (#307) (2cf72f3), closes #5
why
When a run parks on a human gate the engine writes paused_waiting_human to the STORE, returns ErrRunPaused, and only then does the goroutine carrying it call Deregister on its way out. Between those, the public signal says "resumable" while the handle is still held — and the studio and the pipeline-board sidebar offer Resume on exactly that signal. A resume landing in the window failed with
run "..." is already registered, which reads as a bug to an operator and is one to any automated chain…
3.9.0 (2026-07-28)
Features
bots: modernize (Morphy) — gate-to-gate lots against an oracle it cannot rewrite (f7b72e9)
why
The unit of work is the LOT, not the package. A dependency-upgrade pipeline whose failure path is revert-this-package-and-continue cannot express a runtime move that touches nine hundred files at once, so this is a separate bot rather than a widening of that one.
golden-master: a spent held-out set becomes published evidence (8ce6b92)
why
The sealed set protected the hardening loop and then vanished with the run, which left the committed net unable to support its own headline claim. A third party could read '7/7 held-out detected' and had exactly as much reason to believe it as they have to believe any delivery's self-reported figures — which is to say none, and that is precisely the criticism this bot exists to make.
model: move the fleet to the Claude 5 family, and read the generation instead of listing it (1978c3a)
why
Reasoning capability was decided by a list of known model ids. A list is silent when it is wrong: a model absent from it is classified as non-reasoning, extended thinking is never requested, and the run pays full price for a degraded answer without a single warning. claude-opus-5 matched none of the five patterns.
models: audit committed prices against the ones already being fetched (02d84c0)
why
iterion downloads model pricing from the spec aggregator, caches it for 24h, and never reads it: InputCostPerM and OutputCostPerM were parsed and dropped, with no consumer anywhere in the tree. Meanwhile the cost estimator asks a different live source and falls back to a hand-maintained table. Two sources of truth for the same number, never compared.
reviews+deps: one merge gate per repo, and Vetty guards Renovate PRs end-to-end (#306) (eac354f), references #300
why
A repo webhook could only ever launch a single bot per delivery: SelectBot() returns "" as soon as two bots are enabled, so the lane fell back to the hardcoded "review-pr". Co-enabling a dependency guard and a reviewer therefore lost the guard entirely, and — because the shared AuthorAllowlist is the union of every bot's, nil as soon as one bot is open — the guard's author filter was discarded too.
Bug Fixes
golden-master: a spent held-out set is not a broken seal (b14faca)
why
Publishing the set created a third way to have nothing to score, and the harness knew only two. A replay after promotion accused the operator of having cleared the seal, when the set had in fact been scored once and published exactly as designed.
golden-master: enforce the corpus width floor, on distinct references (0b428ec)
why
min_corpus existed only in the campaign prompt. The harness never read it and the gate never checked it: a corpus of three entries passed as long as the three were seen. Same shape as the seal that was guaranteed by a sentence in a skill — an obligation stated to the agent with nothing behind it.
golden-master: fail the gate when the emitted net cannot be replayed (5b23373)
why
The first net this bot emitted was not runnable from a clean checkout. The campaign gitignored harness.py — defensible-looking, since it is a copy of a bundle file — so the committed oracle was references plus a runner shelling out to a script that does not travel. Checked out fresh, verify-oracle.sh exits 2 with 'No such file'.
golden-master: resolve the base URL instead of baking it (b3b6804)
why
The emitted net recorded base_url as a literal. Ports are derived from the repository path — the fix for two checkouts fighting over one port, where the bad case is not a refused start but the second copy capturing the first one's application and recording a net that describes a different tree. A literal therefore pins the net to the machine AND the path that recorded it.
golden-master: the emitted runner exits red when the gate is red (f77b3f0)
why
verify-oracle.sh printed the report and exited 0 no matter what the verdict was. The graph never noticed, because it reads the JSON and computes the conjunction itself — but this script is the entry point for CI and for humans, and both read exit codes. A runner that reports a red gate and exits 0 IS a blind judge, one level above the one this bot was built to catch.
golden-master: the negative control covers the whole corpus (e773e6c)
why
It sampled the first six entries, which left every later entry never once confronted with its own reference. A reference could be stale, or frozen against a world that had since moved, and nothing would say so unless a mutant happened to target it. That is a hole in the one guard whose job is to prove the comparators are not noisy.
model: resolve bare model names deterministically, by consensus (c2d00a3)
why
The bare-name index was built by assigning into a map while ranging over one. Go randomises map iteration, so a model published by several providers resolved to a DIFFERENT provider's numbers on every process start. Five consecutive runs of the same command produced five different prices for glm-5.2, one of them zero.
3.8.1 (2026-07-28)
Bug Fixes
server: a malformed comment no longer costs the review and the merge gate (#305) (189caa6), references #304 #304
why
Two couplings, both hit live on PR #304.
3.8.0 (2026-07-28)
Features
bots: golden-master (Goldy) — behavioural non-regression net that proves it can see (e404343)
why
Records what an existing app observably does, then PROVES the references are not blind with a deterministic mutation counter-test: injected divergences must all be seen, a no-op mutation must leave the oracle silent.
golden-master: binary lane — PDF/spreadsheet capture and the blind-judge diagnostic (238f8ff)
why
Ajoute poppler (pdftotext, pdftoppm) au bundle et un skill binary-lane cable sur la campagne. Les deux archetypes binaires (content_empty, value_change) etaient deja exiges par le harnais ; il manquait l'outillage et le guide.
golden-master: selfcheck mode, mode-aware reports, and a mechanical seal (53a9c18)
why
Trois faiblesses residuelles du run 005.
model: show the rejected payload when a tool call fails (addfc99)
why
Une erreur d'outil qui nomme une propriete manquante n'est pas exploitable sans la charge qui l'a omise. Aujourd'hui la ligne de log dit seulement
Bug Fixes
golden-master: derive the seal path so campaign and gate agree on it (065fe48)
why
The previous fix scoped the seal to the run by forcing GM_SEALED_DIR at the gate. The campaign seals too — the golden-master skill has it run selfcheck — in another process, without that environment. It therefore fell back to the shared gm-holdout path and MOVED the held-out set there, after which the gate looked in the run-scoped path, found nothing, and would have bailed on a seal it had itself broken.
golden-master: emit_runner crashed on shell brace expansion (997a55e)
why
Le DSL expanse les expressions d'environnement, y compris la forme ${VAR:-defaut}, AVANT d'executer le script. La clause de defaut court jusqu'a la premiere accolade fermante -- qui etait celle de la substitution de template suivante. Une accolade mangee, script Python impossible a parser, et le run echouait sur son dernier noeud APRES que la porte ait converge.
golden-master: four defects found by the first real run (2fd1a08)
why
- The harness forced
shon mutant scripts, ignoring the shebang. On most systems /bin/sh is dash, which has nosource: a helper file never loaded, every function it defined was 'not found', and the mutant died with a bare exit 127 giving no hint the interpreter had been swapped. Scripts now run honouring their shebang, with anshfallback when not executable.
- The harness forced
golden-master: per-run seal, and a campaign schema of one field (efb46be)
why
Deux defauts trouves en surveillant le run 006, tous deux de conception.
golden-master: the emitted runner advertised a flag it did not implement (6ca64c2)
why
L'en-tete annoncait un --self-check absent du script, et laissait croire que le mode par defaut ne rejouait pas le contre-test -- alors qu'il fait exactement cela. Un runner qui ment sur ce qu'il fait est un runner que personne ne relit.
runtime: stop warning that bundle skills are absent from the skill library (086d1cf)
why
Le miroir de bibliotheque tourne APRES ceux du bundle et des plugins, qui le supplantent (ADR-059). Quand un bundle fournit ses propres skills, la reference est deja satisfaite -- mais on avertissait quand meme, une ligne par skill a chaque demarrage. C'est vrai, inutile, et ca se lit comme un run casse : le bundle golden-master en emettait six a chaque lancement.
wait out a provider quota window instead of burning 8 pods against it (#304) (41d3330)
why
A terminal node failure was rebuilt as a plain string, so both the classified error code and the original error were destroyed at the point the engine gave up on the node. Two consequences, neither visible from either side alone:
3.7.5 (2026-07-27)
Bug Fixes
review-pr: recover findings when the merge step degrades + honest gate note (#302) (cd2ffeb), references #300
why
Second occurrence, live on PR #300 (run 019fa02b): converge returned
findingsas the prose "See structured findings array." while total_findings said 8. The publish step parsed nothing, so the review published "0 findings kept" with 0 inline comments — 8 real findings never reached the author — and the fail-closed gate reported "1 blocking finding(s) >=high", sending the operator hunting for a finding that was never published. The 0.5.4 prompt hardening was necessary but not sufficient: an…
3.7.4 (2026-07-26)
3.7.3 (2026-07-25)
3.7.2 (2026-07-25)
Bug Fixes
plugin: repair two codeindex rewriter defects found on review (#297) (472f7d0)
why
Both would have shipped broken, and neither is caught by manifest validation.
3.7.1 (2026-07-25)
Bug Fixes
review-pr: harden emit
findingscontract (JSON array, not prose) (#299) (6496bb4), references #292why
Found live on the merge gate's own PR #292 (run 019f98ed): revi/review posted
failure | 1 blocking findingwhile the review body said "0 findings kept". Root cause: the converge/emit LLM returnedfindingsas a PROSE STRING ("4 findings kept (0 critical, 1 high, 2 medium, 1 low)…") instead of the JSON array of objects the schema intends.findings: jsonaccepts a string (the DSL has no object-array type), so nothing rejected it. Downstream, publish_review'sJSON.parse(FINDINGS)fails →…
3.7.0 (2026-07-25)
Features
forge: iterion remote forge refresh — re-sync a connection's grants now (#298) (75c02e9)
why
Operability tool motivated by the merge-gate rollout: after changing a GitHub App's permissions (e.g. granting Commit statuses: write), an operator had to wait for the periodic refresh worker or restart the whole server for iterion to pick up the new grant. This adds a targeted, explicit refresh.
3.6.1 (2026-07-25)
Bug Fixes
forge: request statuses:write in the runtime App token (unblocks merge gate) (#295) (0a3f9ed)
why
Live prod e2e of the merge gate proved the full chain works end-to-end (Revi reviews → bot sends the deterministic gate verdict → server resolves the head SHA → SetCommitStatus), but the status did not land: the GitHub App lacks "Commit statuses: write", so SetCommitStatus returns 403 "insufficient scope". The code handles it exactly as designed — non-fatal, reported in gate_error, logged (
forge gate: … not posted: … insufficient scope) — so the gate advises instead of blocking until the…
3.6.0 (2026-07-25)
Features
plugin: ship codeindex as a builtin plugin (#296) (193a33a)
why
codeindex (https://github.com/maxgfr/codeindex) is a deterministic, zero-dependency repo-indexing engine on npm. This wires it in as a disabled-by- default builtin, alongside the other knowledge-graph explorers.
3.5.2 (2026-07-25)
Bug Fixes
review-pr: second publish_review shell bug — bare double-quote truncates (#294) (c06e0e1), references #293 #293 #292
why
The e2e re-run (after #293) still failed: publish_review produced EMPTY output (exit 0, no forge review, no revi/review status), then publish_health crashed on the empty inputs. Root cause: a python COMMENT in the publish_review body contained bare double-quotes ("high"/"blocker"/"major"). The body is wrapped by the shell in
python3 -c "…", so a bare double-quote ends the string and silently truncates the script. Same trap as backticks; distinct from the #293 questions-array bug (both were…
3.5.1 (2026-07-25)
Bug Fixes
review-pr: publish_review shell exit-127 on multi-question reviews (#293) (34df1c1), references #292 #292
why
The v0.5.0 questions channel passed a JSON array of strings through the publish_review tool node as QUESTIONS={{input.questions}}. A
json-typed field holding an all-string array decodes to []string, which the tool-command substitution SPACE-JOINS instead of JSON-encoding (known engine bug, executor_tool.go:1047) — so the 2nd+ question landed in shell command position and bash exit-127'd, crashing the entire review (no forge review, no revi/review gate status). findings escaped this because…
3.5.0 (2026-07-24)
Features
docs-refresh: /doki is a direct PR-scoped command (amend-on-PR activation) (5d2e481)
why
The /doki comment command switches from board/any to direct/pr, mirroring /revi: a developer commenting /doki on a PR launches docs-refresh directly on the PR head (no tracking card), self-switching to incremental amend via the generic pr_url/base_ref/source_branch the webhook stamps. The manifest half of activating the amend-on-PR trigger — the /command→bot route derives from this invocation, no engine code. v3.5.4.
3.4.0 (2026-07-24)
Features
review-pr: falsifiable questions channel + deterministic Revi merge gate (#291) (ad8e2d6), references #285 #290
why
Two improvements to Revi, motivated by a "0 findings / no comment" review (PR #285) that gave no signal of depth, and by the wish to let Revi arbitrate a merge without an LLM being the yes/no gate.
3.3.0 (2026-07-24)
Features
docs-refresh: agnostic amend-on-PR (v3.5.2) + engine stays bot-agnostic (d184a1f)
why
Doki self-aligns a PR's docs and amends it (pushes onto the PR head + comments) when launched ON a pull request — keyed entirely on the GENERIC PR-context the engine already provides for ANY bot, not on bot-specific engine code:
Bug Fixes
docs-refresh: scope_check bases on run-start HEAD, not oldest reflog (40ae433)
why
In amend-on-PR mode the cloud runner clones the base branch (HEAD=main) then checks out the PR head, so the OLDEST reflog entry is main. scope_check diffed against it and folded the PR author's OWN code into the changed set, raising a phantom writeable-set violation that pinned scope_ok=false — so
convergednever fired and every amend run burned all its passes (live run 019f9429). Base the diff instead on the run-start HEAD: the newest reflog entry that is not one of this run's own `Bot:…studio: "Open child bot" 404 → resolve child path against the right parent (#285) (c7f6aab)
why
Clicking "Open child bot" from the editor inspector could 404 (and, in a follow-up, silently open nothing): the child .bot path is resolved from the parent file with
resolveSubbotSource(parentFilePath, source), butparentFilePath(document storecurrentFilePath) can be null during the short route-hydration window — arriving from Pipelines activates the editor tab one render before EditorTabHost copies the file into the document store. With a null parent, a parent-relativesourcewas…
3.2.0 (2026-07-24)
Features
docs-refresh: v3.4 — drop noop cache, author_docs, mark_issue (native paradigm) (1d2c482)
why
Three more non-essential nodes removed, converging Doki on the native shape (one adaptive agent + a truth gate + the PR tail):
docs-refresh: v3.5 — incremental (git-detected base) + amend-PR modes (1a5eddf)
why
Two alignment strategies so Doki keeps docs fresh cheaply, on the native paradigm (one agent + truth gate + PR):
Bug Fixes
docs-refresh: anchor incremental base detection to the trailer line (bb2291e), references #288
why
git log --grep 'Bot: docs-refresh' matched any commit MENTIONING the trailer in prose — including this bot's own v3.5 feature commit (caught on the first live check against origin/main, where it picked 1a5eddf29 over the real last alignment #288). Anchor to a line start (-E --grep '^Bot: docs-refresh') so only actual trailer lines count. Test now commits a prose-mention after the alignment commit and asserts the base stays the real alignment commit.
studio,desktop: point Documentation links to the Pages site, not the repo docs folder (474eecd)
why
The cloud landing, the About and Backends settings tabs, the desktop menu, and the desktop app-info binding all linked to github.com/.../tree/main/docs (raw repo folder) instead of the published docs site. Point them at https://socialgouv.github.io/iterion/ (and /backends for the backends deep link).
3.1.3 (2026-07-24)
3.1.2 (2026-07-23)
3.1.1 (2026-07-23)
Bug Fixes
feed-watch: make the SSRF guard proxy-aware for sandboxed runs (#287) (bc6c16d)
why
A cloud/sandboxed run reaches the internet through iterion's egress proxy, injected as HTTPS_PROXY and advertised at the runner's own (necessarily private) pod IP — the trusted egress boundary and the secret-redaction point (started even in
network: openwhenever a SecretRewriter is present). urllib then dials the PROXY, not the feed host, so Vigie's socket-level getaddrinfo guard rejected our own proxy as "SSRF-unsafe address <pod-ip>" and every feed failed (run 019f8feb: all 69 fetches…
3.1.0 (2026-07-23)
Features
studio: surface a run's PR/deploy links as headline result-links (#286) (421f901)
why
A run that opens a PR (finalize_mr) or deploys an app (Appy) buried the resulting URL in a node's structured output — nowhere prominent. Surface it like a CI run's "View deployment" button, at the top of the run summary.
3.0.0 (2026-07-23)
⚠ BREAKING CHANGES
- docs-refresh: plan-then-execute comprehensive, asymptote on honest declaration (3.1.0)
Features
docs-refresh: plan-then-execute comprehensive, asymptote on honest declaration (3.1.0) (3b8dd24)
why
3.0 was fast/cheap but under-delivered — it handled the ~10 advisory hints, surveyed shallowly, and honestly-but-myopically declared docs_aligned after one ~15-min pass, producing tiny PRs on a 250-doc corpus with weeks of unread semantic drift. The fix is framing, not a coverage gate: an audited.json exhaustiveness checklist was drafted and REJECTED as exactly the excess determinism 3.0 removed (it makes the agent do bookkeeping instead of the job).
docs-refresh: self-orchestrated coverage — campaign fans out its own subagents (3.2.0) (cd6c92b)
why
A live 3-way benchmark (this repo, 2026-07-23) settled why 3.0/3.1 shipped tiny PRs: a SINGLE campaign agent — exactly like a free native agent handed the same one-liner — self-scopes to the headline docs and misses the long tail. By rising coverage: Doki (~3 commits/pass, docs/ only) < native one-liner (6 fixes, missed cloud + bot READMEs) < native with a demanding prompt (reached the WHOLE corpus). The only run that got there DECOMPOSED into per-cluster sub-auditors ON ITS OWN.
server,studio: full cloud bot editing — team-authored bot store (8850fd6)
why
Adds a writable, team-scoped bot store so the studio editor works in cloud, not only on a local filesystem. The cloud catalog stays baked read-only; tenant bots are editable and forkable.
studio: multi-file bundle editor for cloud bots (4444448)
why
Adds a "Bundle files" drawer (Toolbar, shown only for a botsource:// tenant bot) listing the bundle's files. main.bot opens in the DSL Canvas; skills/*.md, manifest.yaml and any other file edit inline in a Monaco buffer and save per-file to the bot-source store. New files can be added and non-main files removed. Reuses the FileEditDialog Monaco pattern + inferMonacoLanguage; the tab-kind system is untouched.
webhooks: PR-open auto-reviews only (Revi); Billy on /billy with Revi's review (#283) (463646d)
why
Decouple the mutating branch-improve loop (Billy) from PR-open auto-launch: a PR/MR open now ONLY ever auto-reviews (Revi / review-pr). Removes the selectForgePRBot ticket-PR→Billy routing; the merge-queue auto-heal path (NeedsAutoHeal) is unchanged.
Bug Fixes
docs-refresh: budgets sized for self-orchestration (3.2.1) (7213fc6)
why
First 3.2.0 live run (019f8e08) aligned 40 docs across the whole corpus in pass 1 (~70 min / ~$16) — the win — but the old 2h/$60 caps guillotined it mid-pass-2 as a hard failed_resumable BEFORE finalize, and all 40 in-pod commits were lost (the exporter only runs on a clean finalize; engine gap tracked separately). Comprehensive self-orchestrated passes are long: max_duration 2h→6h, max_cost_usd 60→120, max_passes 8→4 so the asymptote reaches GRACEFUL exhaustion (which finalizes + exports +…
native: unique-title prefix must respect the caller's rune budget (89787a9), references #N #N #N
why
The atomic CreateUniqueTitle prepended "#N - " to the desired title without re-truncating, so a pipeline-board title already compacted to 80 runes became 85 once made unique — deterministically failing TestPipelineBoardTaskCreateEnsuresUniqueTitle (the server's list-then-check fallback already re-compacted; the atomic path didn't). CreateUniqueTitle now takes an optional
normalize func(string) stringapplied to every candidate inside the lock; the pipeline board passes compactPipelineTitle so…runner: budget-exceeded acks (no auto-resume) — stop the git-meta clobber (266e6ad)
why
ErrBudgetExceeded fell through to the generic Nak, so a budget-exceeded run (a resumable checkpoint) was auto-redelivered and resumed. That was doubly destructive: the same message carries the same already-spent budget, so a duration cap re-fails instantly in a pod-provisioning loop; and each redelivery re-provisions a FRESH pod whose recordRunGitMeta overwrites the first attempt's good git metadata with base==head — silently destroying the run's exported commits. Live: run 019f8e08 (Doki 3.2)…
server: tenant bot gallery slug — botregistry Path is the bundle dir (6e679a1)
why
tenantBotEntries re-keys a discovered tenant bundle to its store slug, since a forked bot's manifest name (e.g. "docs-refresh") differs from its slug and would otherwise collide with the catalog bot of that name and never surface under its own id. slugFromMaterializedPath wrongly required ≥2 path segments, but botregistry sets Entry.Path to the bundle DIRECTORY ("<root>/<slug>"), a single segment — so the slug was never applied and the tenant bot vanished from the gallery / 404'd on GET.
studio: reach editor home with tabs open + in-editor "Duplicate & edit" (d877725)
why
Two cloud editor UX gaps:
studio: read-only editor for catalog bots in cloud (no more 500 on Save) (05102a0)
why
Opening a baked catalog bot in the cloud editor bound a filesystem path (/opt/iterion/bots/<bot>/main.bot); Save then hit /api/files/save and 500'd with "permission denied" (the image is read-only, and cloud has no writable workspace). Only a team-authored bot (botsource:// path) is writable in cloud.
studio: replace window.confirm/prompt with accessible dialogs (5511004)
why
The a11y source-discipline test bans window.confirm/alert (design-system Don'ts); the bundle-files delete used window.confirm and the new-file / fork-slug flows used window.prompt — reddening main's Tests check on the previous commit. Delete now goes through useConfirm(); the text prompts through a new promise-based usePromptText() hook (styled Dialog + Input, validation, Enter-to-submit), mirroring useConfirm's shape.
