flowchad-runner
Verify named FlowChad flows with Playwright/Navvi, capture per-step screenshot + video
evidence, auto-switch to Navvi on CAPTCHA, upload evidence (best-effort), then post results
to GitHub and write a local report. Each stage runs as a focused subagent with isolated
context. The ICM win here is clean stage contracts + resumability, NOT parallelism:
flows CANNOT run concurrently because they share browser, session, and persona state and
would collide. Stage 03 walks flows one at a time in a sequential loop.
When to Use
- Explicit pre-merge verification for a PR that affects declared flows
- Production verification after deployment
- Weekly or scheduled critical production smoke runs
- Manual local diagnosis when local CAPTCHA behavior is explicitly configured
When Not to Use
- Docs-only, Dependabot, or unaffected PRs: return without creating a preview
- Static-only analysis presented as interactive certification
- Global provider auto-preview enablement; previews are explicit and on-demand
Arguments
| Param | Required | Default | Notes |
|---|
| yes | — | Flow name, or ( for cron; otherwise) |
| yes | — | Target |
| no | (none) | If set, post results comment to this PR |
| no | | | | | — drives URL resolution + deploy-wait |
Parse positionally from
:
$1=flow-name $2=repo $3=pr-number $4=trigger
.
Use the literal
when a cron/manual invocation has no PR number.
Result Contract
| State | Meaning |
|---|
| Required steps passed with real browser evidence |
| Browser evidence demonstrated a product or flow defect |
| A required target, deploy, browser, persona, or credential was unavailable |
| PR is docs-only, Dependabot, or does not affect a declared flow |
Interactive flows can never pass from curl, static HTML, or bundle inspection. Read
references/interactive-contract.md when creating or
upgrading
and flow definitions.
What it does
5-stage ICM procedure (all sequential — no parallel stages):
| Stage | Mode | Description |
|---|
| 01-preflight | subagent | Validate contract, select affected flow, resolve target/selective preview, deploy-wait, browser/persona check |
| 02-load-flows | subagent | Read , validate each flow file exists, load flow YAML |
| 03-walk-flows | subagent | Sequential loop: for each flow one-at-a-time — connect browser, run steps, per-step screenshot, expect-judgement, CAPTCHA→Navvi, transcript |
| 04-upload-evidence | subagent | Best-effort: push screenshots/GIFs to evidence backend, collect URLs |
| 05-report | inline | Aggregate results, post PR comment, create issues on failure, write local report, emit outcome marker |
Handoff locations
All handoffs live in the resolved repo workspace (
, see Workspace resolution):
text
$WORKDIR/.procedure-output/flowchad-runner/{stage}/handoff.md
Stage 01 writes the resolved run context (URL, persona, flow list). Each subagent stage
receives ONLY the handoff paths its CONTEXT.md lists as inputs — never orchestrator history.
Execution
Run stages strictly sequentially, one after another. There are NO parallel Task launches
in this procedure. Spawn exactly one Task per subagent stage and await it before the next.
Workspace resolution (ALWAYS run this first)
The contract, flows, handoffs, and reports all live inside a checkout of the target repo.
Missions run in a workspace that does NOT contain that checkout — never assume the current
directory is the repo. Resolve it deterministically before anything else:
bash
SKILL_DIR="$HOME/.claude/skills/flowchad-runner"
[ -d "$SKILL_DIR" ] || SKILL_DIR="$(pwd)/.claude/skills/flowchad-runner"
if [ -f .flowchad/config.yml ]; then
# Already inside a checkout (local/manual runs)
WORKDIR="$(pwd)"
else
REPO_NAME="${REPO##*/}"
REPO_DIR="/tmp/flowchad-${REPO_NAME}"
if [ ! -d "$REPO_DIR/.git" ]; then
# Plain https URL — inline credentials would bypass git-credential-pylot
git clone "https://github.com/${REPO}.git" "$REPO_DIR" 2>/dev/null \
|| gh repo clone "$REPO" "$REPO_DIR"
fi
cd "$REPO_DIR"
git fetch origin --prune
if [ "$TRIGGER" = pr ] && [ -n "$PR_NUMBER" ] && [ "$PR_NUMBER" != none ]; then
# Validate the PR's OWN contract — flows/config may change in the PR itself
git fetch origin "pull/${PR_NUMBER}/head:flowchad-pr-${PR_NUMBER}" --force
git checkout -f "flowchad-pr-${PR_NUMBER}"
else
DEFAULT_BRANCH=$(gh repo view "$REPO" --json defaultBranchRef -q .defaultBranchRef.name)
git checkout -f "$DEFAULT_BRANCH"
git reset --hard "origin/${DEFAULT_BRANCH}"
fi
WORKDIR="$REPO_DIR"
fi
[ -f "$WORKDIR/.flowchad/config.yml" ] || {
echo "[pylot] outcome=\"flowchad blocked: $REPO has no .flowchad/config.yml at the resolved ref\" status=blocked"
exit 0
}
Every subsequent command (validator, stage handoffs, reports) runs with
.
Because subagent Tasks do NOT inherit the orchestrator's
, stage prompts must carry
ABSOLUTE paths: substitute the literal values of
and
into the
template below — never pass
or
relative forms.
Stages 01 → 04 (sequential subagents)
For each stage, spawn one Task. The Task prompt must be self-contained:
- Include only the stage's input handoff paths
- Include the path to the stage's CONTEXT.md
- Do NOT pass orchestrator history or prior reasoning
Task prompt template:
text
You are running stage {NN}-{name} of the flowchad-runner procedure.
Workspace: cd {WORKDIR} before any step (absolute path — all relative paths resolve there).
Read your stage instructions:
{SKILL_DIR}/stages/{NN}-{name}/CONTEXT.md
Your inputs:
{list each input handoff path this stage needs — absolute, under {WORKDIR}}
Write your output to:
{WORKDIR}/.procedure-output/flowchad-runner/{NN}-{name}/handoff.md
Execute all steps in CONTEXT.md. Write handoff.md before exiting.
and
are the absolute values resolved in Workspace resolution.
Do not start the next stage until the current one completes. If stage 01 cannot resolve a
TARGET_URL, or stage 01's deploy-wait fails, or stage 02 finds the flow file missing, emit
the matching outcome marker and stop (those stages also create GitHub issues themselves).
Before stage 01, verify the checked-in contract deterministically (from
):
bash
cd "$WORKDIR"
mkdir -p .procedure-output/flowchad-runner
MODE="$TRIGGER"
[ "$MODE" = merge ] && MODE=production
[ "$MODE" = pr ] && MODE=preview
[ "$MODE" = manual ] && MODE=local
python3 "$SKILL_DIR/scripts/validate_contract.py" \
--mode "$MODE" --repo "$REPO" --format json \
> .procedure-output/flowchad-runner/contract.json
Any validator error blocks production, preview, and cron certification. Do not silently fall
back to legacy
, template identity, or localhost for those modes.
Stage 05 (inline)
Run stage 05 yourself in the orchestrator. Read CONTEXT.md:
text
.claude/skills/flowchad-runner/stages/05-report/CONTEXT.md
Aggregate the prior handoffs, post the PR comment / create failure issues, write the local
report file, and emit the
marker from the orchestrator (never a subagent).
Stage handoff chain
text
01-preflight ─► 02-load-flows ─► 03-walk-flows ─► 04-upload-evidence ─► 05-report (inline)
(URL, (validated (sequential (evidence URLs, (PR comment,
persona, flow YAML) per-flow walk, best-effort) issues, local
FLOWS_TO_RUN) results+transcript) report, marker)
Exit paths
- Success: stage 05 emits
[pylot] outcome="flowchad {flow} on {repo}: all flows passed" status=success
- Failure: stage 05 emits
[pylot] outcome="flowchad {flow} on {repo}: {N} flow(s) failed" status=failed
(failure issues already created in stage 05)
- Blocked: stage 01 (invalid contract / no URL / deploy failed / no browser) or stage 02
(flow missing) emits
[pylot] outcome="flowchad blocked: {reason}" status=blocked
and the chain stops.
- N/A: stage 01 detects an unaffected/docs-only/Dependabot PR and emits
[pylot] outcome="flowchad N/A: no affected interactive flow" status=success
without a deploy.
Hard Rules
- All stages run SEQUENTIALLY — exactly one Task at a time, awaited before the next.
There is NO parallel fan-out anywhere in this procedure.
- Flows are walked ONE AT A TIME inside stage 03 — never one subagent per flow. Flows
share browser/session/persona state and would collide if run concurrently.
- Stage 05 runs inline — the marker MUST come from the orchestrator,
never a subagent.
- Never pass full orchestrator context into subagent Task prompts — inputs only.
- Each stage writes handoff.md before the next stage reads it.
- Do not skip stages — every stage executes even if its action is "nothing to do"
(e.g. evidence upload with backend still writes a handoff).
- A broken step is a finding, not a crash — stage 03 continues collecting evidence after
a step error; only flow-level pass/fail is judged.
- NO Quest, no external dashboards. Reporting = the local report file + GitHub only.
- Interactive PASS requires browser evidence. Static/curl diagnostics can support a
or result, never .
- Production-critical controls are never optional or skipped. Missing CAPTCHA/Navvi
capability is , not a pass and not a production skip.
- Preview creation is selective. Never enable provider auto-previews; create at most one
on-demand preview for an explicitly dispatched relevant PR when no staging target exists.
- Cron uses . Failures create or update a deduplicated issue with browser
evidence; the public skill does not own the scheduler.
Reference files
- — architecture overview
stages/NN-name/CONTEXT.md
— per-stage inputs, task, output contract
references/interactive-contract.md
— target configuration and CAPTCHA/i18n examples
scripts/validate_contract.py
— deterministic environment/flow contract validator