What a day of running Baron on a real Azure DevOps project found, fixed.
0.41.0 was dogfooded end to end on a live project — issue, branch, a run that stopped halfway and
was resumed, pull request, merge, sync, close — with the agent driving Baron and a human watching.
Six defects came out of it. Five are fixed here; the sixth is filed with its shape (#223).
A merge is judged after Azure finishes it
Azure answers the pull-request completion request with the PR as it was — still Active — and
merges a moment later. Baron read that answer as the verdict and reported a real merge as
MERGE_FAILED; a user who believed it would have run task-land again against an already-merged
pull request. mergePullRequest now waits for Azure to finish (bounded, 20 seconds, re-reading
the PR each second), returns the merge commit, and — if the wait runs out — says how long it
waited and to read the PR status before retrying.
CI runs are filtered where the provider is
Listing Azure runs by branch fetched the newest N builds and filtered the branch afterwards, so
top was spent on other branches and a build queued a moment ago on the asked-for branch was
invisible: the same query returned nothing at limit 5 and the build at limit 50, and trace said
"no CI run" about a build Baron itself had just triggered. The branch now reaches Azure as a
filter and results come newest-queued first, so runs still queued or running count as the latest.
trace finds the pull request's own builds
Providers build a pull request on a ref of their own — refs/pull/<n>/merge — not on the source
branch, so a trace that asked by branch could never see a PR's validation builds. The ci contract
learns the join (RunQuery.pullRequestId, Run.pullRequestId; the conformance suite pins it), the
Azure adapter maps it to the merge ref, trace asks for the branch and then the pull request, and
when neither has a run it names both places. baron_ci_read op=runs takes pullRequestId too.
The post-merge build on the target branch is not joined yet; #223 says what that needs.
A failed run says where it stopped, and what it had done
Over MCP, a failed recipe run carried its run id only in structuredContent, which the model does
not reliably see; the agent reported "no run id" and read the journal by hand. And a resumed run
that had replayed three steps and then failed returned the error alone — its "Replayed …" lines
were gone, and the agent concluded nothing had been replayed. The error text now ends with the run
id and the exact resume call, and the messages the run produced before it stopped arrive as the
second text block, exactly as they would on success. A provider's own error is wrapped as
RECIPE_STEP_FAILED so it carries the same.
init keeps the credentials template committable
A hand-written .baron/credentials.* in a project's .gitignore also matched
credentials.example, so the template init means to be committed never reached the repository.
init now evaluates the ignore patterns against the template the way git does and appends
!.baron/credentials.example when something would hide it.
What Baron got right on the day
Across three failed resume attempts of the same run nothing was repeated and Azure ended with one
pull request; trace's missing reasons were read correctly by the agent; and when Azure did not
close the item on merge, task-sync-report named it and task-move closed it.