Skip to content

fix(flows): check the repository the way its CI does, and never throw the work away - #98

Merged
kjgbot merged 5 commits into
mainfrom
fix/flow-resilient-checks
Sep 19, 2026
Merged

kjgbot merged 5 commits into
mainfrom
fix/flow-resilient-checks

Conversation

@kjgbot

@kjgbot kjgbot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Why

The generated software-factory flow ran one hard-coded recipe — npm ci && npm test behind a lockfile detector — and treated its exit code as the whole verdict. f.run has no retry policy, so a failing test step ended the run as retries_exhausted with the agents' finished work unpushed and no test output anywhere a user could see. Two production runs today:

  • AgentWorkforce/cloud acbe30c1 — the test step failed on AssertionError: @cloud/core dist missing — run npm run -w @cloud/core build before this test. Cloud's CI builds core first (ci.yml:333); the recipe never did, so every run against cloud fails at this step whatever the ticket. Building core first in the same sandbox made the whole suite pass, agents' commit included.
  • AgentWorkforce/relay 139d1a46 — 18 tests failed inside the flow. In a clean shell of the same sandbox the agents' branch passed all 3,227 (base: 3,173, 0 failed). One cause is proven: Cloud's executor passes its own git hardening (protocol.allow=never, GIT_CONFIG_GLOBAL=/dev/null) to every step, which reproduces fatal: transport 'file' not allowed in sandbox-repo.test.ts. (The executor side is being fixed separately in cloud.)

Also: Codex committed summary.md into the branch in both runs, as in AgentWorkforce/flows#454.

What changes in the generated flow

Finding the check command, for any language. .relayflow/check.sh comes from, in order:

  1. the author's checkCommand constant in the flow;
  2. a .relayflow/check.sh the repository commits;
  3. a discovery agent that reads CI config, Makefile/justfile/Taskfile and AGENTS.md/CLAUDE.md/README (runs before the implementer, so the base commit is checked the same way);
  4. an ecosystem default written into the file: make/just test target, Node (unchanged pnpm/Yarn/Bun/npm logic), Cargo, Go, Python (uv/poetry/pip), Ruby, Maven, Gradle, .NET, Mix;
  5. otherwise none: nothing runs, and the PR says so.

Running it resiliently. Every check step prints one token (pass/fail/timeout/none) and exits 0. Full output goes to .relayflow/*.log, and its tail goes to stderr for the journal. The script runs without Cloud's GIT_CONFIG_* hardening, and stops itself at 14 minutes, before the 15-minute f.run lease would kill the run.

Repair, then compare. On failure, a repair agent (up to 2 attempts) reads the log and fixes missing setup by editing check.sh (as CI does) or fixes bugs in the change. It never skips or weakens tests. Whatever still fails is run on the base commit in a throwaway worktree:

base branch outcome
— pass / none ready PR, reviews, needs_human
fail fail draft PR with both outputs, reviews still run, needs_human
pass fail draft PR with the output, step_failed (the flow's verdict on its own work, like a failed review)
unknown fail draft PR saying it could not compare, reviews, needs_human

The branch is always pushed; work is never thrown away. The PR body is summary.md plus a ## Checks section containing the verdict, the script that ran, the output tails and any repair notes. In the traditional preset, the fixer's revision is checked the same way. If it breaks checks that passed before it, it is still pushed, the PR goes back to draft with the report, and the run ends step_failed.

Working files. summary.md, the plans and reviews, .relayflow/ and the local kit files are excluded via .git/info/exclude, using root-anchored patterns so a docs/summary.md is still committed. If an agent committed them anyway, they're removed before every push by a plumbing commit built from a private index, so staged but uncommitted work is never swept in. Only files absent from the base commit are removed, so a repository's own summary.md survives.

Local kit: START-HERE, the run requirements and the preflight exemptions are updated to match.

Verification

  • npx vitest run (web): 19 files, 182 tests pass. The new real-shell tests use real git repositories for resolve (every ecosystem, none), run (pass/fail/none/timeout, Cloud's git config not reaching tests with a positive control), base comparison (regression vs pre-existing, worktree cleanup, unknown base), exclude, drop (keeps a pre-existing summary.md, never sweeps staged work, identity fallback), and the report. The generated flow is also executed with a mocked context for each branch.
  • tsc --noEmit (web): clean.
  • flows check: all 3 presets × cloud/local pass on 2.0.15 and 2.0.17. flows check also passes a flow referencing an undefined variable, so the generated flows were additionally checked with strict tsc against the 2.0.17 surface types: all 6 clean, and a deliberately broken control fails.

Follow-ups (not in this PR)

  • An optional "test command" field in onboarding. For now the author can set checkCommand in the flow or commit .relayflow/check.sh.
  • Cloud executor: run the flows CLI in a clean environment (separate cloud PR).
  • A saved check script per deployment, so later runs skip discovery.

🤖 Generated with Claude Code


Note

High Risk
Changes core generated flow behavior (git, push/PR, check execution, and completion reasons) across all presets; mistakes could publish wrong PR state or mis-classify check failures.

Overview
Replaces the software-factory flow’s single hard-coded npm test step with repository-aware checks via .relayflow/check.sh, plus agents and shell helpers that resolve, run, repair, and compare results against the base commit.

Check pipeline. Before implementation, the flow settles a check script from checkCommand, a committed .relayflow/check.sh, a check-discovery agent (CI/Makefile/README), or ecosystem defaults (make, Node, Rust, Go, Python, etc.). Checks return one token (pass/fail/timeout/none) and always exit 0; logs live under .relayflow/. On failure, check-repair runs up to twice; if checks still fail, the same recipe runs on the starting commit to tell regressions from pre-existing breakage. Failed runs push draft PRs with a ## Checks report (not retries_exhausted with unpushed work). Regressions end step_failed; shared base failures still open drafts and can continue to review.

Git hygiene. Working artifacts (summary.md, plans, .relayflow/, kit files) are excluded via .git/info/exclude and stripped from the branch before push if agents committed them. PR bodies use .relayflow/pr-body.md. Fixer revisions get the same check/repair path; breaking previously passing checks drafts the PR again.

Product tweaks. New agent roles and onboarding labels; local/cloud wall-clock budget 1h → 2h; onboarding and START-HERE copy describe CI-aligned checks and checkCommand. Extensive vitest coverage for the new shell commands and generated flow behavior.

Reviewed by Cursor Bugbot for commit 38312f6. Bugbot is set up for automated code reviews on this repo. Configure here.

… the work away

The generated flow ran one hard-coded recipe (npm ci && npm test behind a
lockfile detector) and treated its exit code as the whole verdict, so a
failing test step killed the run with the agents' finished work unpushed.
Two production runs died that way:

- AgentWorkforce/cloud acbe30c1: cloud's suite needs @cloud/core built
  first, which its CI does and the recipe did not.
- AgentWorkforce/relay 139d1a46: the branch passed all 3,227 tests in a
  clean shell of the same sandbox, but 18 failed inside the flow, among
  them `transport 'file' not allowed` from Cloud's own git configuration.

The check step is now language-agnostic and resilient:

- The check script (.relayflow/check.sh) comes from the author's
  checkCommand, a committed .relayflow/check.sh, a discovery agent that
  reads CI config, Makefile/justfile and README, or an ecosystem default
  (make/just test, Node, Cargo, Go, Python, Ruby, Maven, Gradle, .NET, Mix).
- Each check step prints one token and exits 0; output goes to
  .relayflow/*.log and its tail to stderr for the journal. The check runs
  without Cloud's GIT_CONFIG_* hardening and stops itself before the
  15-minute f.run lease.
- A repair agent (two attempts) fixes missing setup in check.sh or bugs in
  the change, never by weakening tests.
- What still fails is compared with the base commit in a throwaway
  worktree. The branch is always pushed and the pull request opens as a
  draft with the verdict, the script and both outputs in its body. A
  change that breaks checks the base passes ends step_failed; a failure
  the base shares ends needs_human after the reviews.
- Working files (summary.md, plans, reviews, .relayflow/, kit files) are
  excluded through .git/info/exclude and, if an agent committed them
  anyway, removed from the branch before every push. Both production runs
  had Codex commit summary.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 4a28df17-b947-4228-b9ab-dd03cefdca82


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployed!

Environment URL
Web https://e2e238cc-agentrelay-web.agent-workforce.workers.dev

This is a Cloudflare Workers preview version of this PR's build.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread web/lib/flow-workflows.ts
Comment thread web/lib/flow-workflows.ts Outdated
… setup

Check discovery ran once, before the implementer. On a repository with no
way to test itself it resolved "none", and a change that added the first
package.json and test script shipped without any check running: dev run
53f1bc98 opened AgentWorkforce/cloud-e2e-sandbox#31 with tests the flow
never ran ("no checks ran"). A "none" is now resolved again after the
implementer; any other answer is kept, so the base commit and the branch
are still checked the same way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread web/lib/flow-workflows.ts
A software-factory run against a repository the size of
AgentWorkforce/cloud spends most of an hour in the implementer before its
CI-equivalent checks even start (dev run af3069c9: implementer still
working at 39 minutes of 60, checks not started). One hour leaves no room
for checks, repair and a base comparison. Cloud's own run ceiling is being
raised to match in a separate AgentWorkforce/cloud PR; until that lands the
hosted run is still cut at its ~55-minute cloud limit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n macOS, own new checks

Three Bugbot findings on the resilient check step:

- Base comparison used a cleaner tree. The branch's checks ran in a tree the
  implementer had built in; the base ran in a fresh worktree, so a default
  that builds nothing (make test, python3 -m pytest) could fail there for
  missing setup and turn a real regression into "pre-existing". The base is
  now checked in the same tree (ignored build products stay), then the branch
  is restored by name and anything the base run changed in tracked files is
  discarded; a tree with uncommitted tracked changes still uses a worktree.

- No limiter without `timeout`. macOS ships neither `timeout` nor
  `gtimeout`, so a hung check ran into the 15-minute f.run lease and failed
  the run before anything was pushed. `gtimeout` and a small Perl limiter
  (own process group, whole group stopped, exit 124) are now fallbacks.

- Checks the change introduced read as pre-existing. When the first resolve
  found nothing and the change added the test setup, the base lacks those
  files and always fails. Such a failure is now baseline "new": reported as
  the change's own and ended step_failed, like a regression.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1854914. Configure here.

Comment thread web/lib/flow-workflows.ts
…it is committed

The same-tree base comparison checked out the base commit in place and then
ran .relayflow/check.sh from that tree. A repository that commits the script
had it replaced or deleted by the checkout, so the base ran a different
recipe or reported none. The branch's script is now copied aside first and
both sides run it, as the worktree fallback already did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kjgbot
kjgbot merged commit add1bb6 into main Sep 19, 2026
5 checks passed
@kjgbot
kjgbot deleted the fix/flow-resilient-checks branch September 19, 2026 02:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant