fix(workflows): persist structuredOutput on NodeOutput so $node.output.field works for Pi#1636
Conversation
…be (coleam00#1359) The pre-flight binary smoke does a bare `bun build --compile` — it deliberately skips `scripts/build-binaries.sh` to stay fast. That means packages/paths/src/bundled-build.ts retains its dev defaults, including BUNDLED_IS_BINARY = false. version.ts branches on BUNDLED_IS_BINARY: when true it returns the embedded string; when false it calls getDevVersion(), which reads package.json at `SCRIPT_DIR/../../../../package.json`. Inside a compiled binary SCRIPT_DIR resolves under `$bunfs/root/`, the walk produces a CWD- relative path that doesn't exist, and the smoke aborts with "Failed to read version: package.json not found" — a false positive. Hit during the 0.3.8 release attempt: the real Pi lazy-load fix was working end-to-end; the smoke test was the only thing failing. Use --help instead. It exercises the same module-init graph (so it still catches the real failure modes the skill lists — Pi package.json init crash, Bun --bytecode bugs, CJS wrapper issues, circular imports under minify) but has no dev/binary branch, so no false positive. Also add a longer comment block explaining why --help is preferred, so this doesn't get "normalized" back to `version` by a future drive-by.
The brew path of /test-release runs `brew uninstall` in Phase 5 to leave the system in its pre-test state. For operators using the dual-homebrew pattern (renamed brew binary at `/opt/homebrew/bin/archon-stable` so it coexists with a `bun link` dev `archon`), that uninstall wipes the Cellar dir the `archon-stable` symlink points into → `archon-stable` becomes dangling → `brew cleanup` sweeps it away on the next brew op. Next time the operator wants stable, they have to manually re-run `brew-upgrade-archon`. Fix: make the skill aware of `archon-stable` and restore it transparently. - Phase 2 item 4: detect the `archon-stable` symlink before any brew op; export `ARCHON_STABLE_WAS_INSTALLED=yes` so Phase 5 knows to restore it. Only triggers for the brew path (curl-mac/curl-vps don't touch brew so they leave `archon-stable` alone). - Phase 5 brew path: after `brew uninstall + untap`, if the flag was set, re-tap + re-install + rename. Verifies the restored `archon-stable` reports a version and warns (non-fatal) if the rename target is missing. Documents the tradeoff: the restored version is "whatever the tap ships today", not necessarily the pre-test version — usually that's what the operator wants (the release they just tested becomes stable) but the back-version-QA case requires a manual `brew-upgrade-archon` after. - Phase 1 confirmation banner now mentions that `archon-stable` will be preserved so the operator isn't surprised by the reinstall during Phase 5. No changes to curl-mac/curl-vps paths. No changes to Phase 4 test suite.
… a compiled binary (coleam00#1360) v0.3.9 made Pi boot-safe: lazy-loading its imports meant `archon version` no longer crashed on `@mariozechner/pi-coding-agent/dist/config.js`'s module-init `readFileSync(getPackageJsonPath())`. That's what the `provider-lazy-load.test.ts` regression test guards. The fix was only half the problem though. When a Pi workflow actually runs, sendQuery() triggers the dynamic import — and Pi's config.js module-init fires then, hitting the exact same ENOENT on `dirname(process.execPath)/package.json`. Discovered by running `archon workflow run test-pi` against a locally-compiled 0.3.9 binary: [main] Failed: ENOENT: no such file or directory, open '/private/tmp/package.json' at readFileSync (unknown) at <anonymous> (/$bunfs/root/archon-providertest:184:7889) at init_config Boot-safe ≠ runtime-safe. The `/test-release` run for 0.3.9 passed because it only exercised `archon-assist` (Claude); Pi was never actually invoked on the released binary. Fix: before the dynamic `import('@mariozechner/pi-coding-agent')` in sendQuery, install a PI_PACKAGE_DIR shim. Pi's config.js checks `process.env.PI_PACKAGE_DIR` first in its `getPackageDir()` and short-circuits the `dirname(process.execPath)` walk. We write a minimal `{name, version, piConfig:{}}` stub to `tmpdir()/archon-pi-shim/package.json` (idempotent — existsSync check) and set the env var. Pi only reads `piConfig.name`, `piConfig.configDir`, and `version` from that file, all optional, so the stub surface is genuinely minimal. Localized to PiProvider: no global state, no mutation of any shared config, no upstream fork. Claude and Codex providers are unaffected (their SDKs don't have this class of module-init side effect). Verified end-to-end: built a compiled archon binary with this patch, ran `archon workflow run test-pi --no-worktree` (Pi workflow with model `anthropic/claude-haiku-4-5`), got a clean response. Before the patch, same binary crashed at `dag_node_started` with the ENOENT above. Regression test added: asserts `PI_PACKAGE_DIR` is set after sendQuery hits even its fast-fail "no model" path. Together with the existing `provider-lazy-load.test.ts` (boot-safe) this covers both halves.
… and Codex (coleam00#1361) Both binary resolvers previously stopped at env-var + explicit config and threw a "not found" error when neither was set. Users who followed the upstream-recommended install flow (Anthropic's `curl install.sh` for Claude, `npm install -g @openai/codex`) still had to manually set either `CLAUDE_BIN_PATH` / `CODEX_BIN_PATH` or the corresponding config field before any workflow could run. Add a tier-N autodetect step between the explicit config tier and the install-instructions throw. Purely additive: env and config still win when set (precedence covered by new tests). On autodetect miss, the same install-instructions error fires as before. Claude probe list (verified against docs.claude.com "Uninstall Claude Code → Native installation" section): - $HOME/.local/bin/claude (mac/linux native installer) - $USERPROFILE\.local\bin\claude.exe (Windows native installer) Codex probe list (verified against openai/codex README; npm global- install puts the binary at `{npm_prefix}/bin/<name>` on POSIX, `{npm_prefix}\<name>.cmd` on Windows): - $HOME/.npm-global/bin/codex (user-set `npm config set prefix`) - /opt/homebrew/bin/codex (mac arm64 with homebrew-node) - /usr/local/bin/codex (mac intel / linux system node) - %APPDATA%\npm\codex.cmd (Windows npm global default) - $HOME\.npm-global\codex.cmd (Windows user-set prefix) Not probed (explicit override still required): - Custom npm prefixes — `npm root -g` would need a subprocess per resolve, too much surface for a probe helper - `brew install --cask codex` — cask layout isn't a PATH binary - Manual GitHub Releases extracts — placement is user-determined - `~/.bun/bin/codex` — not documented in openai/codex README Pi provider intentionally has no equivalent change: the Pi SDK is bundled into the archon binary (no subprocess), so there's no "binary" to resolve. Pi auth lives at `~/.pi/agent/auth.json` which the SDK already finds by default, and the PR A shim (`PI_PACKAGE_DIR`) handles the package-dir case via Pi's own documented escape hatch. E2E verified: removed both config entries from ~/.archon/config.yaml, rebuilt compiled binary, ran `archon workflow run archon-assist` and a Codex workflow. Logs showed `source: 'autodetect'` for both, responses returned cleanly.
…ry autodetect test The native-installer autodetect test computed its expected path from process.env.HOME, but the implementation uses node:os homedir(). On Windows, HOME is typically unset (Windows uses USERPROFILE), so the test fell back to '/Users/test' while the resolver returned the real home dir — making the spy's path-equality check fail and breaking CI on windows-latest. Mirror the implementation by importing homedir() from node:os and joining with node:path so the expected path matches the actual platform-resolved home and separator. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ver (coleam00#1365) Reported in coleam00#1365: a user running `archon serve` with DISCORD_BOT_TOKEN set but the "Message Content Intent" toggle disabled in the Discord Developer Portal saw the entire server crash with `Used disallowed intents`. Discord rejects the gateway connection (close code 4014) when a privileged intent is requested without being enabled, and the unguarded `await discord.start()` propagated the error all the way up, taking the web UI down with it. Wrap discord.start() in try/catch — log the failure with an actionable hint (special-cased for the disallowed-intent error) and continue running. Other adapters and the web UI come up regardless. The shutdown handler already uses optional chaining (`discord?.stop()`) so nulling discord after a failed start is safe. Other adapters (Telegram, Slack, GitHub, Gitea, GitLab) have the same unguarded-start pattern but are out of scope for this fix — addressing them is tracked separately. Also expanded the Discord setup docs with a caution callout that names the exact error string and the new log event so users can grep for both. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…0#1362) * docs(script-nodes): add dedicated guide and teach the archon skill how to write them Script nodes (script:) have been a first-class DAG node type since v0.3.3 but were documented only as one-liners in CLAUDE.md and a CI smoke test. Claude Code reading the archon skill would see "Four Node Types: command, prompt, bash, loop" and reach for bash+node/python one-liners instead of a proper script node — losing bun's --no-env-file isolation, uv's --with dependency pins, and the .archon/scripts/ reuse story. - New packages/docs-web/src/content/docs/guides/script-nodes.md mirroring the structure of loop-nodes.md / approval-nodes.md: schema, inline vs named dispatch, runtime/deps semantics, scripts directory precedence (repo > home), extension-runtime mapping, env isolation, stdout/stderr contract, patterns, and the explicit list of ignored AI fields. - guides/authoring-workflows.md and guides/index.md updated so the new guide is discoverable from both the node-types table and the guides landing page. - reference/variables.md calls out the no-shell-quote difference between bash: and script: substitution — a subtle correctness trap when adapting a bash pattern into a script node. - Sidebar order bumped +1 on hooks/mcp-servers/skills/global-workflows/ remotion-workflow to slot script-nodes at order 5 next to the other node-type guides. - .claude/skills/archon/SKILL.md: replaces stale "Four Node Types" (which also silently omitted approval and cancel) with the accurate seven, with a script-node code block showing both inline and named patterns. - references/workflow-dag.md: full Script Node section covering dispatch, resolution, deps, stdout contract, and the list of AI-only fields that are ignored; validation-rules list updated. - references/dag-advanced.md and references/variables.md: retry-support line corrected; no-shell-quote note added. - examples/dag-workflow.yaml: added an extract-labels TypeScript script node and updated the header comment. * fix(docs): review follow-ups for script-node guide - skills example: extract-labels was reading process.env.ISSUE_JSON which is never set; use String.raw`$fetch-issue.output` so the upstream bash node's JSON is actually consumed - guides/script-nodes.md + skills/workflow-dag.md: idle_timeout is accepted but ignored on script (and bash) nodes — executeScriptNode only reads node.timeout. Clarify that script/bash use `timeout`, not idle_timeout - archon-workflow-builder.yaml: prompt enumerated only bash/prompt/command/loop, so the AI builder could never propose script or approval nodes. Add both (plus examples + rule about script output not being shell-quoted) and regenerate bundled defaults - book/dag-workflows.md + book/quick-reference.md + adapters/web.md: fill in the node-type references that were missing script, approval, and cancel. adapters/web.md also overclaimed "loop" in the palette — NodePalette.tsx only drags command/prompt/bash, so note that the other kinds are YAML-only
…nv gaps, add good-practices + troubleshooting (coleam00#1363) * fix(skill/when): document the full `when:` operator set and compound expressions The skill reference previously stated "operators: ==, != only" which is materially wrong — the condition evaluator supports ==, !=, <, >, <=, >= plus && / || compound expressions with && binding tighter than ||, plus dot-notation JSON field access. An agent authoring a workflow from the skill would think half the operators don't exist. Replaces the single-sentence section with a structured reference covering: - All six comparison operators (string and numeric modes) - Compound expressions with precedence rules and short-circuit eval - JSON dot notation semantics and failure modes - The fail-closed rules in full (invalid expression, non-numeric side, missing field, skipped upstream) Grounded in packages/workflows/src/condition-evaluator.ts. * feat(skill): document Approval and Cancel node types Approval and cancel nodes are first-class DAG node types (approval since the workflow lifecycle work in coleam00#871, cancel as a guarded-exit primitive) but the skill never described either one. An agent reading the skill and asked to "add a review gate before implementation" or "stop the workflow if the input is unsafe" would fall back to bash + exit 1, losing the proper semantics (cancelled vs. failed, on_reject AI rework, web UI auto-resume). Approval node coverage (references/workflow-dag.md, SKILL.md): - Full configuration block with message, capture_response, on_reject - The interactive: true workflow-level requirement for web UI delivery - Approve/reject commands across all platforms (CLI, slash, natural language) and the capture_response → $node-id.output flow - Ignored-fields list + the on_reject.prompt AI sub-node exception Cancel node coverage (references/workflow-dag.md, SKILL.md): - Single-field schema (cancel: "<reason>") - Lifecycle: cancelled (not failed); in-flight parallel nodes stopped; no DAG auto-resume path - The "cancel: vs bash-exit-1" decision rule (expected precondition miss vs. check itself failing) - Two canonical patterns — upstream-classification gate, pre-expensive-step gate Validation-rules list updated to enumerate approval/cancel constraints (message non-empty, on_reject.max_attempts range 1-10, cancel reason non-empty), plus a forward note that script: joins the mutually-exclusive set once PR coleam00#1362 lands. Placement in both files is after the Loop section and before the validation section, so this commit stays additive with respect to PR coleam00#1362's Script node insertion between Bash and Loop — rebase is clean. * feat(skill): document workflow-level fields beyond name/provider/model The skill's Schema section previously showed only name, description, provider, and model at the workflow level — which is most of a stub. Agents asked to "use the 1M-context Claude beta" or "run this under a network sandbox" or "add a fallback model in case Opus rate-limits" had no way to discover that any of these fields existed at the workflow level. Adds a comprehensive Workflow-Level Fields section covering: - Core: name, description, provider, model, interactive (with explicit callout that interactive: true is REQUIRED for approval/loop gates on web UI — a common footgun) - Isolation: worktree.enabled for pin-on/pin-off (the only worktree field at workflow level; baseBranch/copyFiles/path/initSubmodules are config.yaml only, so a cross-reference points there) - Claude SDK advanced: effort, thinking, fallbackModel, betas, sandbox, with explicit per-node-only exceptions (maxBudgetUsd, systemPrompt) - Codex-specific: modelReasoningEffort (with note that it's NOT the same as Claude's effort — this has confused users), webSearchMode, additionalDirectories - A complete worked example combining sandbox + approval + interactive All fields cross-referenced against packages/workflows/src/schemas/workflow.ts and packages/workflows/src/schemas/dag-node.ts. * feat(skill/loop): document interactive loops and gate_message Interactive loop nodes pause between iterations for human feedback via /workflow approve — used by archon-piv-loop and archon-interactive-prd. The skill's Loop Nodes section previously omitted both interactive: true and gate_message entirely, so an agent writing a guided-refinement workflow wouldn't know the feature exists or that gate_message is required at parse time. Adds: - interactive and gate_message rows to the config table (marking gate_message as required when interactive: true — enforced by the loader's superRefine) - A dedicated "Interactive Loops" subsection explaining the 6-step iterate-pause-approve-resume flow - Explicit call-out that $LOOP_USER_INPUT populates ONLY on the first iteration of a resumed session — easy to miss and a common surprise - Workflow-level interactive: true requirement for web UI delivery (loader warning otherwise) so the full-flow example is complete - Note that until_bash substitution DOES shell-quote $nodeId.output (unlike script bodies) — called out since the audit surfaced this inconsistency * fix(skill/cli): complete the CLI command reference with missing lifecycle commands The CLI reference previously documented only list, run, cleanup, validate, complete, version, setup, and chat — missing nearly every workflow lifecycle command an agent needs to operate a paused, failed, or stuck run. The interactive-workflows reference assumed these commands existed without actually documenting them. Adds full documentation for: - archon workflow status — show running workflow(s) - archon workflow approve <run-id> [comment] — resume approval gate (also populates $LOOP_USER_INPUT on interactive loops and the gate node's output when capture_response: true) - archon workflow reject <run-id> [reason] — reject gate; cancels or triggers on_reject rework depending on node config - archon workflow cancel <run-id> — terminate running/paused with in-flight subprocess kill - archon workflow abandon <run-id> — mark stuck row cancelled without subprocess kill (for orphan-cleanup after server crashes — matches the coleam00#1216 precedent) - archon workflow resume <run-id> [message] — force-resume specific run (auto-resume is default; this is for explicit override) - archon workflow cleanup [days] — disk hygiene for old terminal runs (with explicit callout that it does NOT transition 'running' rows, a common confusion) - archon workflow event emit — used inside loop prompts for state signalling; documented so agents don't invent their own mechanism - archon continue <branch> [flags] [msg] — iterative-session entry point with --workflow and --no-context flags Also: - Adds --allow-env-keys flag to the `workflow run` flag table with audit-log context and the env-leak-gate remediation use case - Adds an "Auto-resume without --resume" note disambiguating when --resume is needed vs. when auto-resume handles it - Adds --include-closed flag to `isolation cleanup`, which was previously missing; converts the flag list to a structured table - Explains the cancel/abandon distinction (live subprocess vs. orphan) All grounded in packages/cli/src/commands/workflow.ts, continue.ts, and isolation.ts. * feat(skill/repo-init): add scripts/ and state/, three-path env model, per-project env injection The repo-init reference was missing two first-class .archon/ directories (scripts/ since v0.3.3, state/ since the workflow-state feature) and had nothing to say about env — the coleam00#1 thing a user hits on first-run when their repo has a .env file with API keys. Directory tree updates: - Adds .archon/scripts/ with the extension->runtime rule (.ts/.js -> bun, .py -> uv) so agents know where to put named scripts referenced by script: nodes. - Adds .archon/state/ with explicit "always gitignore" callout — these are runtime artifacts, not source. Previously undocumented in the skill. - Adds .archon/.env (repo-scoped Archon env) and distinguishes it from the target repo's top-level .env. - Adds a "What each directory is for" list so the structure isn't just a tree with no narrative. .gitignore guidance: - state/ and .env added as must-gitignore (state/ matches CLAUDE.md and reference/archon-directories.md — skill was lagging). - mcp/ demoted to conditional — gitignore only if you hardcode secrets. New "Three-Path Env Model" section: - ~/.archon/.env (trusted, user), <cwd>/.archon/.env (trusted, repo), <cwd>/.env (UNTRUSTED, target project — stripped from subprocess env). - Precedence (override: true across archon-owned paths) and the observable [archon] loaded N keys / stripped K keys log lines so operators can verify what actually happened. - Decision tree for where to put API keys vs. target-project env vs. things Archon shouldn't touch. - Links to archon setup --scope home|project with --force for writing to the right file with timestamped backups. New "Per-Project Env Injection" section: - Documents both managed surfaces: .archon/config.yaml env: block (git-committed, $REF expansion) and Web UI Settings → Projects → Env Vars (DB-stored, never returned over API). - Names every execution surface that receives the injected vars: Claude/Codex/Pi subprocess, bash: nodes, script: nodes, and direct codebase-scoped chat. - Documents the env-leak gate with all 5 remediation paths so an agent hitting "Cannot register: env has sensitive keys" knows the options. Grounded in CHANGELOG v0.3.7 (three-path env + setup flags), v0.3.0 (env-leak gate), and reference/security.md on the docs site. * fix(skill/authoring-commands): correct override paths and add home-scoped commands The file-location and discovery sections described an override layout that does not match the actual resolver. It showed: .archon/commands/defaults/archon-assist.md # Overrides the bundled and claimed `.archon/commands/defaults/` was where repo-level overrides lived. In fact the resolver (executor-shared.ts:152-200 + command- validation.ts) walks `.archon/commands/` 1 level deep and uses basename matching — putting `archon-assist.md` at the top of `.archon/commands/` is the canonical way to override the bundled version. The `defaults/` subfolder is a Archon-internal convention for shipping bundled defaults, not a user-facing override pattern. Also, home-scoped commands (`~/.archon/commands/`, shipped in v0.3.7) were completely absent — agents authoring personal helpers wouldn't know they could live at the user level and be shared across every repo. Changes: - File Location section now shows all three discovery scopes (repo, home, bundled) with precedence ordering and 1-level subfolder rules - Duplicate-basename rule documented as a user error surface - Discovery and Priority section rewritten with accurate 3-step lookup order — no more references to the nonexistent defaults/ override path - Adds the Web UI "Global (~/.archon/commands/)" palette label note so users authoring helpers for the builder know what to expect No code changes — this is a pure fix of stale/incorrect skill reference material. * feat(skill): add workflow good-practices and troubleshooting reference pages Closes two gaps from the audit. The skill previously had zero guidance on designing multi-node workflows (what to avoid, what to reach for first, how to structure artifact chains) and zero guidance on where to look when things go wrong (log paths, env-leak gate remediations, orphan-row cleanup, resume semantics). New references/good-practices.md (9 Good Practices + 7 Anti-Patterns): - Use deterministic nodes (bash:/script:) for deterministic work, AI for reasoning — the single biggest quality lever - output_format required whenever downstream when: reads a field — the most common source of "workflow silently routes wrong" - trigger_rule: none_failed_min_one_success after conditional branches — the classic bug where all_success fails because a skipped when:-gated branch doesn't count as a success - context: fresh requires artifacts for state passing — commands must explicitly "read $ARTIFACTS_DIR/..." when downstream of fresh - Cheap models (haiku) for glue, strong for substance - Workflow descriptions as routing affordances - Validate (archon validate workflows) + smoke-run before shipping - Artifact-chain-first design - worktree.enabled: true for code-changing workflows (reversibility) - Anti-patterns with before/after YAML examples for each (AI-for-tests, free-form when: matching, context: fresh without artifacts, long flat AI-node layers, secrets in YAML, retry on loop nodes, tiny max_iterations, missing workflow-level interactive:, tool-restricted MCP nodes) New references/troubleshooting.md: - Log location (~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl) with jq recipes for common queries (last assistant message, failed events, full stream) - Artifact location for cross-node handoff debugging - 9 Common Failure Modes, each with root cause + concrete fix: - $BASE_BRANCH unresolvable - Env-leak gate (5 remediations) - Claude/Codex binary not found (compiled-binary-only) - "running" forever (AI working / orphan / idle_timeout) - Mid-workflow failure and auto-resume semantics - Approval gate missing on web UI (workflow-level interactive:) - MCP plugin connection noise (filtered by design) - Empty $nodeId.output / field access (4 causes) - Diagnostic command cheat sheet (list, status, isolation list, validate, tail-log, --verbose, LOG_LEVEL=debug) - Escalation protocol (version + validate + log tail + CHANGELOG + issue) SKILL.md routing table now dispatches "Workflow good practices / anti-patterns" and "Troubleshoot a failing / stuck workflow" to the new references so an agent can find them without having to know they exist. * docs(book): update node-types coverage from four to all seven The book is the curated first-contact reading path (landing page → "Get Started" → /book/). Both dag-workflows.md and quick-reference.md were stuck on "four node types" — missing script, approval, and cancel. A user reading the book as their first introduction would form an incomplete mental model, then find three more node types in the reference section later with no explanation of when they arrived. book/dag-workflows.md: - "four node types" → "seven node types. Exactly one mode field is required per node" - Table now lists Command, Prompt, Bash, Script, Loop, Approval, Cancel with one-line "when to use" for each, and cross-links to the dedicated guide pages for Script / Loop / Approval - New sections below the table for Script (inline + named examples with runtime and deps), Approval (with the interactive: true workflow-level note that's easy to miss), and Cancel (guarded-exit pattern) — keeping the existing narrative shape for Bash and Loop book/quick-reference.md: - Node Options table now includes script, approval, cancel rows - agents row added (inline sub-agents, Claude-only) - New "Script-specific fields" and "Approval-specific fields" subsections so the cheat-sheet is actually complete rather than pointing users elsewhere for the required constraints - Retry row callout that loop nodes hard-error on retry — previously omitted - bash timeout note widened to cover script timeout (same semantics) Both files are docs-web content; the CI build on the docs-script-nodes PR (coleam00#1362) previously validated the Starlight build path with a similar table addition, so this should render clean. * fix(skill/cli): remove nonexistent \`archon workflow cancel\`, fix workflow status jq recipe Two accuracy issues from the PR code-reviewer (comment 4311243858). C1: \`archon workflow cancel <run-id>\` does NOT exist as a CLI subcommand. The switch at packages/cli/src/cli.ts:318-485 dispatches on list / run / status / resume / abandon / approve / reject / cleanup / event — running \`archon workflow cancel\` hits the default case and exits with "Unknown workflow subcommand: cancel" (cli.ts:478-484). Active cancellation is only available via: - /workflow cancel <run-id> chat slash command (all platforms) - Cancel button on the Web UI dashboard - POST /api/workflows/runs/{runId}/cancel REST endpoint cli-commands.md: removed the \`### archon workflow cancel <run-id>\` subsection; kept the \`abandon\` subsection but made it explicit that abandon does NOT kill a subprocess. Added a call-out box at the bottom of the abandon section explaining where to go for actual cancellation. troubleshooting.md "running forever" section: split the original cancel-vs-abandon advice into three bullets — Web UI / CLI abandon (for orphans, no subprocess kill) / chat \`/workflow cancel\` (for live runs that need interruption). Added an explicit "there is no archon workflow cancel CLI subcommand" parenthetical since the wrong command was being suggested in flow. I1: the \`archon workflow list --json\` diagnostic used an incorrect jq filter. workflow list's --json output (workflow.ts:185-219) has shape { workflows: [{ name, description, provider?, model?, ... }], errors: [...] } with no \`runs\` field — \`jq '.workflows[] | select(.runs)'\` returns empty unconditionally. Replaced with \`archon workflow status --json | jq '.runs[]'\`, which matches the actual shape of workflowStatusCommand at workflow.ts:852+ ({ runs: WorkflowRun[] }). Also tightened the narration to distinguish JSON from human-readable status output. No change to the commit history in this PR — these are follow-up fixes to claims I introduced in earlier commits of this branch (f10b989 for C1, 66d2b86 for I1). * fix(skill): remove env-leak gate references (feature was removed in provider extraction) C2 from the PR code-reviewer (comment 4311243858). The pre-spawn env-leak gate was removed from the codebase during the provider-extraction refactor — see TODO(coleam00#1135) at packages/providers/src/claude/provider.ts:908. Zero hits for --allow-env-keys / allowEnvKeys / allow_env_keys / allow_target_repo_keys across packages/. The CLI's parseArgs (cli.ts:182-208) has no --allow-env-keys option, and because parseArgs uses strict: false, an unknown --allow-env-keys would be silently ignored rather than error. What remains accurate and is NOT touched: - Three-Path Env Model section (user/repo archon-owned envs are loaded; target repo <cwd>/.env keys are stripped from process.env at boot) still correctly describes current behavior, grounded in packages/paths/src/strip-cwd-env.ts + env-integration.test.ts - Per-Project Env Injection section (Option 1: .archon/config.yaml env: block; Option 2: Web UI Settings → Projects → Env Vars) is unchanged — both remain the sanctioned way to get env vars into subprocesses Removed claims (all three files): - cli-commands.md: --allow-env-keys flag row in the workflow run flags table - repo-init.md: the "Env-leak gate" subsection at the end of Per-Project Env Injection listing 5 remediations (all of which reference UI/CLI/ config surfaces that don't exist). Replaced with a succinct callout that explains the actual current behavior — target repo .env keys are stripped, workflows that need those values should use managed injection — so the reader still gets the "where to put my env vars" answer - troubleshooting.md: the "Cannot register: codebase has sensitive env keys" section (error message that can no longer be emitted) If the env-leak gate is ever resurrected per TODO(coleam00#1135), the docs can be re-added then. The CHANGELOG v0.3.0 entry describing the gate is a historical record of past behavior and does not need to be rewritten. * fix(skill/troubleshooting): correct JSONL event type names and field name C3 from the PR code-reviewer (comment 4311243858). The troubleshooting reference's event-types table used _started / _completed / _failed suffixes, but packages/workflows/src/logger.ts:19-30 shows the actual WorkflowEvent.type enum is: workflow_start | workflow_complete | workflow_error | assistant | tool | validation | node_start | node_complete | node_skipped | node_error The second jq recipe also queried `.event` but the discriminator is `.type`. Fixes: - Event table: renamed columns (_started → _start, _completed → _complete, _failed → _error). Explicitly called out the field name as `type` so the reader knows what jq selector to use - Replaced the "tool_use / tool_result" row with a single `tool` row and listed its actual payload fields (tool_name, tool_input, duration_ms, tokens) — tool_use/tool_result are SDK message kinds that appear within the AI stream, not top-level log event types - Added a `validation` row (was missing; it's emitted by workflow-level validation calls with `check` and `result` fields) - Removed `retry_attempt` row — this event type is not emitted to the JSONL file. Retry bookkeeping goes through pino logs, not the workflow log file - Added an explicit callout that loop_iteration_started / loop_iteration_completed (and other emitter-only events) go through the workflow event emitter + DB workflow_events table, NOT the JSONL file. Pointed readers to the DB or Web UI for loop-level detail. This distinguishes the two parallel event systems — easy to conflate (store.ts:11-17 uses _started/_completed/_failed for the DB side, logger.ts uses _start/_complete/_error for JSONL) - Fixed the "all failed events" jq recipe: .event → .type and _failed → _error - Minor cleanup: the inline "tool_use events" mention in the "running forever" section said the wrong event name — updated to "tool or assistant events in the tail" Grounded in packages/workflows/src/logger.ts (canonical JSONL event shape) and packages/workflows/src/store.ts (the parallel DB event naming, which the reviewer correctly flagged as different and worth keeping distinct). * fix(skill): two stragglers from the code-reviewer audit Cleanup of two references that slipped through the earlier C1 and C3 fixes: - references/troubleshooting.md:126: \`node_failed\` → \`node_error\` (the "Node output is empty" diagnostics section references the JSONL log, which uses the logger.ts enum — not the DB workflow_events table which does use \`node_failed\`). The C3 fix corrected the event table and one jq recipe but missed this inline mention. - references/interactive-workflows.md:106: removed \`archon workflow cancel <run-id>\` (nonexistent CLI subcommand) from the troubleshooting bullet. This was pre-existing before the hardening PR but fell within the C1 remediation scope. Replaced with the correct triage: reject (approval gate only) vs abandon (orphan cleanup, no subprocess kill) vs chat /workflow cancel (actual subprocess termination). Grounded in the same sources as the earlier C1/C3 commits: packages/cli/src/cli.ts:318-485 (no cancel case) and packages/workflows/src/logger.ts:19-30 (JSONL type enum). * feat(skill): point to archon.diy as the canonical docs source The skill had no reference to archon.diy (the live docs site built from packages/docs-web/). Several reference files said "see the docs site" without naming the URL, leaving the agent to guess or grep the repo for the hostname. An agent with the skill loaded should know that when the distilled reference pages don't cover a case, the full canonical docs are one WebFetch away. SKILL.md: new "Richer Context: archon.diy" section between Routing and Running Workflows. Covers: - When to reach for the live docs (longer examples, tutorial framing, features the skill only mentions in passing, "where's that documented?" user questions) - URL map — 13 starting points covering getting-started, book (tutorial series), guides/ (authoring + per-node-type + per-node-feature), reference/ (variables, CLI, security, architecture, configuration, troubleshooting), adapters/, deployment/ - Precedence: skill refs first (context-cheap, tuned for agents), docs site as escalation. Prevents agents defaulting to WebFetch when a local skill ref already covers the answer Also upgrades the 5 existing generic "docs site" mentions across reference files to concrete archon.diy URLs with anchor fragments where helpful: - good-practices.md: Inline sub-agents pattern → archon.diy/guides/ authoring-workflows/#inline-sub-agents - troubleshooting.md: "Install page on the docs site" → archon.diy/ getting-started/installation/ - workflow-dag.md: "Workflow Description Best Practices" → anchor link; sandbox schema reference → archon.diy/guides/authoring-workflows/ #claude-sdk-advanced-options - repo-init.md: Security Model reference → archon.diy/reference/ security/#target-repo-env-isolation (deep-link into the section that covers the <cwd>/.env strip behavior) URL source of truth: astro.config.mjs:5 (site: 'https://archon.diy'). URL structure mirrors packages/docs-web/src/content/docs/<section>/ <page>.md — verified by the 62 pages the docs build produces.
…#1395) Anthropic's Opus 4.7 landed 2026-04-16; on the Anthropic API, opus / opus[1m] now resolve to 4.7 with a 1M context window at standard pricing. Using the alias instead of the hard-pinned claude-opus-4-6[1m] lets bundled default workflows auto-track the recommended Opus version. No explicit effort is set, so nodes inherit the per-model default (xhigh on 4.7, high on 4.6).
…m00#1398) * fix(workflow): migrate piv-loop plan handoff to $ARTIFACTS_DIR (coleam00#1380) The create-plan node used a relative path (.claude/archon/plans/{slug}.plan.md) that the AI agent would sometimes write to a different location, breaking all downstream nodes that glob for the plan file. Migrated all plan/progress file references to $ARTIFACTS_DIR/plan.md and $ARTIFACTS_DIR/progress.txt, matching the pattern used by archon-fix-github-issue and other workflows. Changes: - Replace slug-based plan path with $ARTIFACTS_DIR/plan.md in create-plan node - Replace ls -t glob discovery with direct $ARTIFACTS_DIR/plan.md reads in refine-plan, code-review, and fix-feedback nodes - Replace empty-string guard with file-existence check in implement-setup bash - Migrate progress.txt references in implement loop to $ARTIFACTS_DIR/ - Add explicit plan/progress paths in finalize node - Regenerated bundled-defaults.generated.ts Fixes coleam00#1380 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(workflow): address review findings in archon-piv-loop - Rename 'Step 2: Write the Plan' to 'Step 2: Plan File Location' to eliminate the duplicate heading that collided with Step 3's identical title in the create-plan node - Guard implement-setup against a 0-task plan file: exit 1 with a clear error when no '### Task N:' sections are found, preventing a silent no-op implement loop - Remove 2>/dev/null from code-review commit so pre-commit hook failures and other stderr are visible to the agent instead of silently swallowed - Replace '|| true' on git push in finalize with an explicit WARNING echo so push failures (auth, upstream conflict, no remote) surface to the agent rather than being silently ignored - Regenerate bundled-defaults.generated.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(workflows): regenerate bundled defaults to match opus[1m] alias The bundle was stale relative to the YAML sources after coleam00#1395 merged — check:bundled was failing CI. Regenerated; no YAML edits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cutor (coleam00#1403) PIV Task 1: Adds three new tests in a dedicated describe block 'executeDagWorkflow -- final status derivation' covering the anyFailed branch (dag-executor.ts ~line 2956) that previously had no direct test: - one success + one independent failure calls failWorkflowRun (not completeWorkflowRun) - multiple successes + one failure calls failWorkflowRun (not completeWorkflowRun) - trigger_rule: none_failed skips dependent node but anyFailed still marks run failed Fixes coleam00#1381.
New reference for the archon skill: a single-glance lookup of which parameter works on which node type, an intent-based "how do I..." table, a consolidated silent-failure catalog, and an inline agents: section (previously only referenced via archon.diy). Purpose is complementary, not duplicative: - workflow-dag.md remains the authoring guide - dag-advanced.md remains the hooks/MCP/skills/retry deep-dive - good-practices.md remains the patterns and anti-patterns - parameter-matrix.md is the grep-this-first lookup when you know the outcome you want but not which field gets you there Also registers the new reference in SKILL.md routing table.
Add explicit references to .github/PULL_REQUEST_TEMPLATE.md in both CONTRIBUTING.md and CLAUDE.md, plus a reminder to link issues with Closes/Fixes/Resolves so they auto-close on merge. Repo-triage runs were flagging dozens of partially-filled or unlinked PRs each cycle.
…riage (coleam00#1428) * feat(workflows): add maintainer-standup workflow for daily PR/issue triage Daily morning briefing that pulls origin/dev, triages all open PRs and assigned issues against direction.md, and surfaces progress vs. the previous run. Designed for live-checkout use (worktree.enabled: false) so it can read its own state. Layout under .archon/maintainer-standup/: - direction.md (committed) — project north-star: what Archon IS / IS NOT. Drives PR P4 polite-decline classification with cited clauses. - README.md / profile.md.example — setup docs and template for new maintainers. - profile.md, state.json, briefs/YYYY-MM-DD.md — gitignored, per-maintainer. Engine: - 3 parallel gather scripts in .archon/scripts/maintainer-standup-*.ts (git-status, gh-data, read-context) — bun runtime, JSON stdout. - Synthesis node: command file with output_format schema for { brief_markdown, next_state }. - Persist node: tiny inline bun script writes both to disk. Run-to-run continuity: state.json carries observed_prs/issues snapshots, so the next run can detect what merged, what closed, what the maintainer shipped, and which carry-over items aged past N days. Also adds .archon/** to the ESLint global ignore list (matches the existing .claude/skills/** pattern) since .archon/ is user content and not part of any tsconfig project. * fix(maintainer-standup): address CodeRabbit review on coleam00#1428 - gh-data: bump --limit 100 → 1000 on all_open_prs and warn loudly when the cap is hit; preserves the observed_prs invariant the next-run "resolved since last run" diff depends on. (CodeRabbit critical) - maintainer-standup.md: clarify P1 CI signal — the gathered payload only carries mergeStateStatus, not statusCheckRollup; for borderline P1s, drill in via `gh pr checks <n>`. (CodeRabbit minor) - workflow.yaml persist: write briefs under local YYYY-MM-DD (sv-SE locale) instead of UTC ISO date, so an evening run doesn't file tomorrow's brief and break recent_briefs lookups. (CodeRabbit minor) - workflow.yaml persist: wrap state/brief writes in try/catch; on failure dump brief_markdown and next_state to stderr so a 5-minute Sonnet synthesis isn't lost to a transient disk error. (CodeRabbit minor) - gh-data + git-status: switch from execSync (shell-string) to execFileSync (argv array) for git/gh invocations. Defense-in-depth against shell metacharacters in values that pass through (esp. the gh_handle from profile.md). (CodeRabbit nitpick)
Add optional `tags: string[]` to `workflowBaseSchema`. Explicit values take precedence over keyword inference; `tags: []` suppresses inference end-to-end; omitting the field falls back to inference (backwards compatible). Non-array values warn-and-ignore matching the sibling `worktree`/`additionalDirectories` patterns.
…ows under maintainer/ (coleam00#1430) * feat(workflows): add maintainer-review-pr and group maintainer workflows under .archon/workflows/maintainer/ Adds the maintainer-review-pr workflow — a Pi/Minimax-based PR triage flow that gates on direction alignment, scope focus, and PR-template quality before doing any deep review. If the gate clears, runs the five review aspects (code/error-handling/test-coverage/comment-quality/ docs-impact) as parallel Archon nodes and auto-posts a synthesized review comment. If the gate fails (direction conflict, multiple concerns, sprawling scope), drafts a polite-decline comment and pauses for the maintainer's approval before posting. Reorganizes the existing maintainer-standup workflow into the same subfolder so all maintainer-facing workflows live together. Subfolder grouping is supported by the workflow loader (1 level deep, resolution by filename). What lands: - .archon/workflows/maintainer/maintainer-standup.yaml (moved from .archon/workflows/maintainer-standup.yaml) - .archon/workflows/maintainer/maintainer-review-pr.yaml (new) - .archon/commands/maintainer-review-{gate,code-review,error-handling, test-coverage,comment-quality,docs-impact,synthesize,report}.md (new, Pi-tuned variants of the existing review-agent commands so they avoid Claude-only Task / sub-agent patterns) Pi/Minimax integration: - Uses provider: pi, model: minimax/MiniMax-M2.7 — verified via the e2e-minimax-smoke test that Pi correctly routes to Minimax (session jsonl confirms provider=minimax) and that Pi's best-effort output_format parser handles the gate's nested schema. - Two test runs landed real comments: a direction-decline on PR coleam00#1335 and a deep-review on PR coleam00#1369. Both were posted to GitHub via the workflow's gh pr comment node. * chore(workflows): also group repo-triage under .archon/workflows/maintainer/ repo-triage is the third maintainer-facing workflow alongside maintainer-standup and maintainer-review-pr; group it in the same subfolder for consistency. Subfolder resolution is by filename so the workflow name is unchanged.
…r unmapped providers (coleam00#1284) Closes coleam00#1096. - Switch Pi provider model lookup from pi-ai's getModel() (static catalog only) to ModelRegistry.create(authStorage).find() so user-configured custom models in ~/.pi/agent/models.json (LM Studio, ollama, llamacpp, custom OpenAI-compatible endpoints) are discoverable. - Remove the local lookupPiModel helper. - For env-var-mapped providers (anthropic, openai, etc.) still throw with a pi /login hint when credentials are missing. For unmapped providers, log pi.auth_missing at info and continue so local models that don't need credentials work without ceremony. - Surface modelRegistry.getError() in the not-found message and emit pi.model_not_found so users debugging custom-provider configs see the real cause (e.g. missing baseUrl in models.json). - Guard AuthStorage.create() and ModelRegistry.create() with try/catch so a malformed ~/.pi/agent/auth.json surfaces with Pi-framed context instead of a raw SDK stack trace. - Document the credential-free path for local providers in ai-assistants.md. Co-authored-by: Matt Chapman <Matt@NinjitsuWeb.com>
…add e2e-minimax-smoke (coleam00#1431) * chore(workflows): group all smoke-test workflows under .archon/workflows/test-workflows/ Move the 7 existing e2e-*.yaml smoke tests plus the new e2e-minimax-smoke test into a dedicated subfolder. Subfolder grouping is supported by the workflow loader (1 level deep, resolution by filename) so workflow names are unchanged. Mirrors the .archon/workflows/maintainer/ split landing in coleam00#1430. Also adds e2e-minimax-smoke.yaml — a sanity check that Pi correctly routes to Minimax M2.7 via the user's local pi auth, and that Pi's best-effort output_format parser handles a small nested schema. Asserts routing by reading the most recent Pi session jsonl rather than asking the model to self-identify (LLMs are unreliable narrators about their own identity, especially when Pi's system prompt mentions other providers as defaults). * fix(e2e-minimax-smoke): address CodeRabbit review on coleam00#1431 - Widen find window from -mmin -3 to -mmin -10. The smoke's three Pi nodes plus the assert can collectively run several minutes on slow networks; 3 minutes was tight enough to false-FAIL on a healthy run. (CodeRabbit minor) - Drop non-deterministic `head -1` over `find` output. find doesn't guarantee any order; on a tie, the wrong file would be picked. Now iterates all matching sessions and breaks on first one carrying the routing signal — any match is sufficient evidence. (CodeRabbit minor) - Replace single-regex `'"provider":"minimax".*"modelId":"MiniMax-M2.7"'` with two separate greps joined by `&&`. JSON field order isn't part of Pi's contract; a future Pi release reordering `provider` and `modelId` in the model_change event would silently false-FAIL the original pattern. The new check is order-independent. (CodeRabbit major)
…oleam00#1432) Six findings, two majors and four minors/nitpicks: - gate.md L17 vs L77: resolved conflicting input-source instructions. Body claimed "all inline, no extra fetch" while a later phase permitted reading PULL_REQUEST_TEMPLATE.md. Now: explicit "one allowed extra read" callout in Phase 1 + matching wording in Gate C. (CodeRabbit major) - gate.md fenced blocks: added missing language identifiers (text/json/ markdown) to satisfy markdownlint MD040. (CodeRabbit minor) - gate.md L155 + read-context.ts: deterministic clock. The 3-day deadline was anchored to prior_state.last_run_at, which can be stale and produce past-dated deadlines. Moved both today and deadline_3d into the read-context.ts output (computed via sv-SE locale → ISO date in local time) and instructed the gate to use $read-context.output.deadline_3d directly. LLMs are unreliable at calendar arithmetic; this avoids it entirely. (CodeRabbit major) - maintainer-review-pr.yaml fetch-diff: dropped 2>/dev/null on gh pr diff so auth / network / deleted-PR failures fail the node instead of feeding an empty diff to the gate. Empty-but-successful diff (PR has no changes) is now an explicit marker the gate can detect. (CodeRabbit minor) - maintainer-review-pr.yaml approve-unclear: added capture_response: true so the maintainer's approve comment flows to the report node. Reject reasoning is already captured by Archon's run record. (CodeRabbit minor) - maintainer-review-pr.yaml post-decline + report.md: the gh pr edit --add-label call previously swallowed all errors with || true and the report still claimed the label was applied. Now writes applied/skipped to $ARTIFACTS_DIR/.label-applied + the gh stderr to .label-error so the report can describe the actual outcome. (CodeRabbit nitpick)
…ume (coleam00#1435) * fix(workflows): approval gate bypass after reject-with-redraft on resume When an approval node was rejected with on_reject.prompt, the synthetic PromptNode built to run the on_reject prompt reused the approval gate's own node ID. executeNodeInternal then wrote a node_completed event with that ID, causing getCompletedDagNodeOutputs to treat the gate as already completed on the next resume — bypassing the human gate entirely. Fix: give the synthetic node the ID `${node.id}:on_reject` so its node_completed event has a distinct step_name that won't match the approval gate slot in priorCompletedNodes. Adds a regression test asserting no node_completed event with the approval gate's ID is written during on_reject execution. Fixes coleam00#1429 * test(workflows): add positive assertion and SSE side-effect comment for on_reject synthetic node Add complementary positive assertion to the regression test to verify that node_completed is written exactly once with step_name 'review:on_reject', ensuring future refactors that suppress the event entirely would be caught. Add inline comment in executeApprovalNode documenting the known SSE side-effect: node_started/node_completed events with nodeId='review:on_reject' flow through the SSE pipeline into the web UI, resulting in a transient phantom node in the execution view. This is cosmetic-only — the human gate contract is preserved. * simplify: reduce duplicate cast pattern in on_reject test assertions
…e checkout (coleam00#1438) * feat(workflows): add mutates_checkout field to skip path-lock for concurrent runs Add `mutates_checkout: boolean` (optional, default true) to the workflow schema. When set to false, the executor skips the path-exclusive lock that serializes all runs on the same working path, allowing N concurrent runs on the same live checkout. The primary use case is `maintainer-review-pr`, which reads shared state but writes only to per-run artifact paths and GitHub PR comments — two parallel reviews of different PRs should not fail with "Workflow already active on this path". Changes: - `schemas/workflow.ts`: add optional `mutates_checkout` field - `loader.ts`: parse and propagate the field (warn-and-ignore on invalid values) - `executor.ts`: wrap path-lock guard in `if (workflow.mutates_checkout !== false)` - `executor.test.ts`: two new tests in the concurrent-run guard suite - `maintainer-review-pr.yaml`: opt in with `mutates_checkout: false` * test(workflows): add loader tests for mutates_checkout parsing - Add 5 tests covering false, true, omitted, and invalid (string "yes") values - Invalid non-boolean values are silently dropped with warn — now explicitly tested - Remove the // end mutates_checkout guard trailing comment (no precedent in file) - Clarify loader comment: "parse/warn pattern" not "warn-and-ignore pattern" to avoid implying the return style matches interactive * simplify: collapse nodeType/aiFields pair into single nonAiNode object in parseDagNode
…es (coleam00#1434) * docs: replace String.raw with direct assignment in script node examples String.raw`$nodeId.output` fails silently when substituted output contains a backtick, terminating the template literal early and producing cryptic parse errors. JSON is valid JS expression syntax, so direct assignment is safe for all valid JSON values including those with backticks. - Replace String.raw pattern in dag-workflow.yaml example - Replace String.raw pattern in archon-workflow-builder.yaml template - Add CAUTION bullet in workflow-dag.md Script Node section - Add Silent Failures item coleam00#14 in parameter-matrix.md - Add Starlight caution aside in script-nodes.md - Extend script bodies bullet in variables.md - Regenerate bundled-defaults.generated.ts Fixes coleam00#1427 * docs: fix Rule 6 in generate-yaml prompt to distinguish bun vs uv patterns Rule 6 still referenced JSON.parse after the example was updated to direct assignment, creating a contradiction for the AI code generator. Update the prose to explicitly distinguish TypeScript/bun (direct assignment) from Python/uv (json.loads), matching the updated embedded example.
…s/experimental/ Move two repo-scoped workflows that were sitting untracked at the workflow root into a dedicated subfolder. Subfolder grouping is supported by the loader (1 level deep, resolution by filename), so workflow names are unchanged and the /release skill still resolves archon-release correctly. Files moved: - archon-fix-github-issue-experimental.yaml — Path-A variant of the issue-fix workflow used today to land coleam00#1434, coleam00#1435, coleam00#1438. - archon-release.yaml — the live release workflow used by the /release skill end-to-end (validate -> binary smoke -> version bump -> changelog -> approval -> commit -> PR -> tag -> Homebrew formula update).
…des (coleam00#1387) executeBashNode previously only merged explicit envVars on top of process.env. The three well-known workflow directories (artifactsDir, logDir, baseBranch) were passed as function parameters and used for compile-time substitution of $ARTIFACTS_DIR / $LOG_DIR / $BASE_BRANCH in the script body, but were never added to the subprocess environment. As a result, any script that relied on shell-runtime expansion — e.g. JSON_FILE="${ARTIFACTS_DIR}/foo.output.json" inside a heredoc, an inherited helper script, or a `bash -c` subshell — saw the variable unset and silently fell back to its default (typically an empty string or "."), writing artifacts to the workflow cwd instead of the nominal artifacts directory. Always build subprocessEnv from process.env plus the three well-known directories, then allow explicit envVars to override. Compile-time substitution behavior is unchanged; existing scripts that do not reference these variables are unaffected; user-supplied envVars still win on conflict.
…oleam00#1426) * fix(workflow): substitute \$nodeId.output refs in approval messages Approval node messages were emitted as raw strings, bypassing the substituteNodeOutputRefs() pass that prompt/bash/loop/cancel nodes all run. This made interactive workflows like atlas-onboard show literal "\$gather-context.output.repo_name" placeholders to humans at HITL gates, leaving them unable to know what they were approving. Fix: rendered the approval.message through substituteNodeOutputRefs once at the top of the standard approval gate path, then used the resolved string in all 4 emission sites (safeSendMessage, createWorkflowEvent, pauseWorkflowRun, event-emitter). Test: new dag-executor.test case wires a structured-output upstream node into an approval node and asserts pauseWorkflowRun receives the substituted message ("Repo: hcr-els | App: CCELS | Port: 3012") rather than the literal placeholders. Repro: any workflow with an approval node whose message references \$nodeId.output[.field]. Observed in the wild on atlas-onboard's confirm-context HITL gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(workflow): extend approval-substitution test to cover all 4 emission sites Per CodeRabbit review: the original test only verified pauseWorkflowRun received the substituted message, but the fix touches 4 emission sites. A future regression at safeSendMessage / createWorkflowEvent / event-emitter would silently leave the test passing while users still saw raw $node.output placeholders. Adds two additional assertions: - platform.sendMessage prompt contains substituted message + does NOT contain literal $gather-context.output placeholders - The persisted approval_requested workflow event's data.message is substituted Event-emitter assertion deferred (no existing pattern for spying on the global emitter in this test file). Two of three secondary surfaces covered closes the practical regression risk — both are user-visible (chat prompt + audit-log event); the emitter is internal only. Test count: 7 pass / 22 expect() (was 18). Full suite 193 pass / 353 expect() — no regressions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…m00#1286) (coleam00#1367) * feat(workflows): expose $LOOP_PREV_OUTPUT in loop node prompts (coleam00#1286) Adds a new substitution variable that carries the previous loop iteration's cleaned output into the next iteration's prompt. Empty on iteration 1; the prior iteration's output (after stripCompletionTags) on iteration 2+. Why: fresh_context: true loops have no way to reference what the previous pass produced or why it failed without dragging the full session forward. $LOOP_PREV_OUTPUT closes that gap with zero session-cost — same trust boundary as $nodeId.output, no new external surface. Changes: - packages/workflows/src/executor-shared.ts: substituteWorkflowVariables accepts a 10th positional loopPrevOutput arg and substitutes $LOOP_PREV_OUTPUT (defaults to ''). - packages/workflows/src/dag-executor.ts: executeLoopNode passes lastIterationOutput on iteration 2+ (and explicit '' on iteration 1 / the first iteration of an interactive resume, since lastIterationOutput is a per-call variable that does not survive resume metadata). - Unit tests: 3 new cases in executor-shared.test.ts. - Integration tests: 2 new cases in dag-executor.test.ts verifying the prompt sent to the AI on iter 1 vs iter 2, and that the value reflects cleaned output (no <promise> tags). - Docs: variables.md, loop-nodes.md (new "Retry-on-failure" pattern), CLAUDE.md variable reference. Backward compatibility: prompts that don't reference $LOOP_PREV_OUTPUT are unaffected. All 843 workflow tests + type-check + lint + format:check + bun run validate pass locally. * docs: address coderabbit review on variables/loop-nodes - variables.md: include $LOOP_PREV_OUTPUT in substitution-order list and availability table to match the new variable row at line 30 - loop-nodes.md: document the interactive-resume exception where the first iteration after an approval-gate resume still receives an empty $LOOP_PREV_OUTPUT regardless of iteration number (per dag-executor.ts L1781-1783 where i === startIteration always clears prev output) * docs(changelog): add Unreleased entry for $LOOP_PREV_OUTPUT (coleam00#1367 review) * test(loop): add resume-from-approval integration test for $LOOP_PREV_OUTPUT (coleam00#1367 review) Per maintainer-review-pr suggestion (Wirasm): two-call integration test covering the resume-from-approval scenario. - Call 1: fresh interactive loop pauses at the gate after iteration 1 and asserts $LOOP_PREV_OUTPUT substitutes to empty on iter 1 (no prior output) plus the gate pause is recorded. - Call 2: resumed run with metadata.approval populated. The first resumed iteration must substitute $LOOP_PREV_OUTPUT to '', NOT to the paused run's iter-1 output (which lived in a different process and is not persisted). $LOOP_USER_INPUT still flows through as normal. Locks the documented invariant at dag-executor.ts:1769-1772. --------- Co-authored-by: voidborne-d <DottyEstradalco@allergist.com>
…oleam00#1457) The brief was missing a key signal — when contributors reply on PRs or issues, the maintainer wouldn't see it explicitly. Empirically reviewed PR replies were buried under aggregate updatedAt timestamps with no indication of WHO replied or WHAT they said. This adds a new "Replies waiting on you" section to the daily brief, sourced from two paginated GitHub API calls scoped by since=last_run_at: - /repos/{o}/{r}/issues/comments PR + issue conversation comments - /repos/{o}/{r}/pulls/comments inline code-review comments Filters applied: - Skip the maintainer's own comments (gh_handle from profile.md) - Skip GitHub bot accounts (login ending in [bot]) — coderabbitai, chatgpt-codex-connector, dependabot, etc. They post a constant churn of automated review tooling that drowns out human replies; the maintainer wants the latter. Output is grouped by PR/issue number with kind classification: - issue comment on a non-PR issue - pr_conversation PR conversation-level comment - pr_review inline code-review comment (most actionable — usually needs a code-level response, so kind upgrades to pr_review whenever review comments arrive on a PR that also has conversation ones) Sorted by recency (newest reply first). Synthesizer reads gh-data.output.replies_since_last_run and renders a section. Verified on a backdated state.json (last_run_at = yesterday morning): 22 human replies on 22 PRs/issues, bot noise filtered (32 → 22 after the [bot] filter). Surfaces exactly the contributor responses to yesterday's review comments and direction questions.
The maintainer-standup brief had no signal for "I already triaged that
PR via maintainer-review-pr 2 days ago" — it just kept listing reviewed
PRs in P1-P4 with no acknowledgement of prior work. Result: maintainer
ends up re-skimming the same PR several mornings in a row.
This adds a shared persistent state file at:
.archon/maintainer-standup/reviewed-prs.json (gitignored, per-maintainer)
shape:
{
"1338": {
"reviewed_at": "2026-04-27T16:34:57Z",
"gate_verdict": "review", // review | decline | needs_split | unclear
"run_id": "..."
},
...
}
Three pieces:
1. WRITER — new `record-review` script node in maintainer-review-pr.yaml,
runs after whichever branch fired (post-review / post-decline /
approve-unclear) with trigger_rule: one_success. Inline bun script;
reads $gate.output.verdict, $ARTIFACTS_DIR/.pr-number, and
$WORKFLOW_ID; appends/upserts the entry. report node now depends on
record-review so the state write happens before the run completes.
2. READER — read-context.ts loads reviewed-prs.json into a new
reviewed_prs field on the standup gather output. Same pattern as
prior_state and recent_briefs.
3. SURFACE — maintainer-standup command file gets a Phase 2h rule:
when listing PRs in P1-P4 / Polite-decline sections, append:
- "✓ reviewed Nd ago" for review-branch entries
- "✓ declined Nd ago" for decline / needs_split branches
- "✓ triaged Nd ago (unclear)" for unclear branch
and a STALENESS marker — compare reviewed_at to PR's updatedAt; if
contributor pushed since the prior review, append
"⚠ contributor pushed since" so the maintainer knows the prior pass
may need to be re-run.
Plus a one-shot backfill script:
.archon/scripts/maintainer-standup-backfill-reviews.ts
Scans the maintainer's gh comments in the last 7 days, pattern-matches
"## Review Summary" / direction-clause-citation / split-up wording, and
populates reviewed-prs.json. Idempotent; existing entries (from real
workflow runs) take precedence over backfilled ones (the writer-node
record is more authoritative than a body-pattern guess). Uses 64MB
maxBuffer on the gh exec because --paginate over 7 days of an active
repo's comments easily exceeds Node's default 1MB.
Backfill verified: 363 comments scanned, 18 matched, 17 unique PRs
populated — exactly the 17 PRs we reviewed via the workflow yesterday.
The new state file is gitignored alongside the existing per-maintainer
files (profile.md, state.json, briefs/).
…oleam00#1460) Both SDKs were ~30 patch releases behind. Validation suite passes (type-check, lint, format, tests across all 10 packages) without code changes. The only sustained Claude SDK behavior change in the range — v0.2.111's options.env overlay/replace flap, since reverted to overlay — is a no-op for Archon, which already passes { ...process.env } as the SDK env.
coleam00#1483) * chore(deps): remove stale package-lock.json to clear Dependabot noise This file was deleted in coleam00#85 (Bun migration) but accidentally re-committed in coleam00#89 unrelated to that PR's actual fix. It hasn't been touched since April and isn't used by anything (CI runs `bun install`), but Dependabot keeps scanning it — every one of the 21 open alerts triaged in coleam00#1353 is against this file, not bun.lock. Removing it closes all 21 alerts. The axios `^1.15.0` override in package.json stays — it's doing real work for the bun tree because @slack/bolt pulls in a vulnerable axios transitively (CVE-2025-62718). Add package-lock.json (and yarn/pnpm lockfiles) to .gitignore so this can't silently slip back in. Closes coleam00#1353 * chore(deps): patch four runtime CVEs in bun.lock via overrides Targets coleam00#1353 alerts that resolve in the actual runtime tree (bun.lock), not just the stale package-lock.json removed in the previous commit. Added overrides: - follow-redirects ^1.16.0 — auth-header leak on cross-domain redirect (GHSA-r4q5-vmmm-2653); via @slack/bolt - path-to-regexp ^8.4.2 — DoS via sequential optional groups (CVE-2026-4926, CVE-2026-4923); via @slack/bolt + claude-agent-sdk - qs ^6.15.1 — arrayLimit bypass DoS (CVE-2025-15284, CVE-2026-2391); via @slack/bolt - flatted ^3.4.2 — prototype pollution in parse() (CVE-2026-33228); dev-only via eslint chain bun audit confirms each resolves to a single non-vulnerable version across the tree. bun run validate green. No code changes — purely transitive bumps; we don't import any of these directly. Skipped (require deeper triage): undici, lodash, picomatch — each has multiple major versions resolved in the bun tree, so a single override would force-downgrade other consumers.
Regression from coleam00#1481 (honor CLAUDE_BIN_PATH in dev mode). The CI workflow set `CLAUDE_BIN_PATH: ~/.local/bin/claude` in YAML `env:` blocks; YAML does not expand `~`, so the literal string was passed to the resolver. Before coleam00#1481, dev mode silently ignored the env var and the SDK auto-resolved its bundled binary — so the broken value was harmless. After coleam00#1481, dev mode honors it, the file-existence check fails on the literal `~`, and the smoke job aborts with "CLAUDE_BIN_PATH is set ... but the file does not exist". Move the env-var assignment into the run-step shell where `$HOME` resolves. Both e2e-claude and e2e-mixed-providers jobs are affected.
After v0.3.10's PR coleam00#1488 squash-merged into main, dev was reset to main's HEAD via git reset --hard. That rewrote dev's history, severing every open PR's merge-base from its original commit and ballooning their diffs (e.g. coleam00#1444 went from +80/-1 to +6626/-300). This merge commit re-attaches the original release commits (db1c005, f51600a) to dev's history while keeping main's content via a 3-way merge. Open PRs' merge-bases revert to their original commits, their diffs shrink back to normal. No force-push: this is a fast-forward over current origin/dev (fd6d75e is one of this commit's parents).
…sh-merge (coleam00#1490) The previous `--ff-only` strategy failed because main's squash commit has a different SHA than dev's release commit, so dev was never fast-forwardable. A reset-based "rewrite dev's history to match main" approach was tried and rejected: it blows up every open PR targeting dev. Their merge-base shifts to a much older commit, and their diffs balloon with all the release content. Confirmed today: PRs that were +80/-1 became +6626/-300. Use a regular merge instead (matches `/release` SKILL Step 9): git checkout dev git pull origin main --no-edit git push origin dev This creates a merge commit on dev that ties main's squash into dev's history. Open PRs' merge-bases stay at their original commits, their diffs stay small, no history is rewritten. The cost is a merge bubble in dev's log, which is the right trade-off. Same pattern applied to commit-formula's dev-sync section. `commit-formula` also gets a `git stash push` before its `git checkout main` — the previous step (`fetch-and-update-formula`) leaves the formula file dirty, which blocks `git checkout` until stashed. KNOWN ISSUE not fixed by this PR: the workflow's formula-update steps (`fetch-and-update-formula` + `commit-formula`) duplicate CI's `update-homebrew` job in `.github/workflows/release.yml`. They race each other on the dev push. See coleam00#1489 for the full analysis and architectural fix (drop the duplicated steps from this workflow). This PR addresses the mechanical bugs that prevented the workflow from completing at all.
The recovery merge `398afe05` (Merge main into dev) resolved the homebrew/archon.rb conflict by running `git checkout main -- homebrew/archon.rb`, which uses the LOCAL `main` branch — at the time, local main was stale (still at the pre-formula-update v0.3.6 state) because origin/main had moved forward via `git push origin dev:main` without local main being fast-forwarded. Result: dev's homebrew/archon.rb regressed to v0.3.6 with v0.3.6 SHAs, even though origin/main, the homebrew-archon tap repo, and the published brew install all correctly point at v0.3.10. User impact: zero — `brew install coleam00/archon/archon` reads from the tap repo (synced correctly during release recovery), not from this template. But the dev template should match reality. Fix: pull origin/main's version of the file (which has the correct v0.3.10 formula) onto dev. Single-file change.
… site (coleam00#1506) * fix(simplify): stage only edited files, forbid scratch artifacts The simplify command used `git add -A`, which sweeps untracked review/ report files (e.g. `review/scope.md` left by upstream review nodes) into the simplification commit. Replace it with explicit per-file staging using the list of paths edited in Phase 2, plus a forbidden-paths list so review artifacts, PR-body scratch files, and anything under `$ARTIFACTS_DIR` cannot leak into the commit. * fix(fix-github-issue): forbid scratch artifacts in create-pr step The inline create-pr prompt told the agent to "stage and commit" any uncommitted changes, which lets transient artifacts from upstream nodes (`.pr-body.md`, `review/scope.md`, scratch reports) land in the implementation commit and PR diff. Replace the loose instruction with explicit per-file staging, a forbidden-paths list, and a rule that any PR body file written for `--body-file` must live at `$ARTIFACTS_DIR/pr-body.md` or `/tmp/` — never inside the worktree. Applied to both the default and experimental variants. * fix(workflows): purge remaining git add -A in worktree-context steps Same class of bug as the simplify and create-pr fixes: every worktree-facing default that used `git add -A` could sweep transient review/scratch artifacts (`.pr-body.md`, `review/scope.md`, `*-report.md`, anything left under `$ARTIFACTS_DIR`) into the commit. Replace with explicit per-file staging plus a forbidden-paths list and a `git status --porcelain` verification step. Touched: - commands: archon-create-pr, archon-finalize-pr, archon-fix-issue, archon-implement-issue, archon-implement-review-fixes - workflows: archon-piv-loop (3 sites), archon-ralph-dag, archon-refactor-safely Intentionally left as `git add -A`: - archon-release.yaml: working tree validated clean before this step; comment already explains why. - archon-adversarial-dev.yaml: operates inside `$ARTIFACTS_DIR/app/`, a dedicated scratch repo, not the user's worktree.
…main sync (coleam00#1492) The SKILL's Step 9 already uses the correct primitive (`git pull origin main`, which creates a regular merge commit), but doesn't explicitly warn against the two trap doors that bit us during v0.3.10: 1. `git pull origin main --ff-only` — used by the experimental archon-release workflow's sync-dev-with-main step. Fast-forward is impossible across a squash merge; the workflow aborted on every release run. 2. `git reset --hard origin/main` — used during today's recovery as a "clean up the merge commit" workaround for coleam00#1. It worked locally but rewriting dev's history severed every open PR's merge-base, ballooning their diffs from <100 lines to thousands. Confirmed: PR coleam00#1444 went from +80/-1 to +6626/-300, restored after a recovery merge that re-attached the original release commits to dev. Add explicit DO-NOT block under the existing "Important" callout. Also document the conflict-resolution gotcha: when the merge conflicts on homebrew/archon.rb, use `git checkout origin/main -- ...` (NOT local `main`, which is typically stale because the release pushes via `git push origin dev:main` without fast-forwarding local main). The workflow-level fixes for both traps landed in coleam00#1490. This is the documentation companion so a future maintainer (or AI agent) doesn't repeat either trap.
* feat(cli): add `archon skill install` command
Adds a standalone `archon skill install [path]` subcommand that copies
the bundled Archon skill files into `<target>/.claude/skills/archon/`,
so users can install or refresh the skill outside the interactive setup
wizard. Defaults to the current directory.
Refactors `copyArchonSkill` out of `commands/setup.ts` into a new
`commands/skill.ts` so the helper can be shared between the wizard and
the new CLI command without pulling in `@clack/prompts`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: add `skill install` to CLAUDE.md, CLI reference, and skills guide
- Add `skill install` command entries to CLAUDE.md CLI section
- Add `skill install` section to docs-web CLI reference page
- Add bundled Archon skill to Popular Skills table in skills guide
Addresses HIGH findings from comprehensive PR review.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): guard bundled-skill import inside skillInstallCommand try block
The dynamic `await import('../bundled-skill')` was outside the try/catch,
so a load failure crashed uncaught instead of returning exit code 1.
Move the import (and the success log + return) inside the try so import,
copy, and post-copy errors all flow through the same controlled path.
Addresses coderabbitai review on PR coleam00#1445.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Thomas Ritter <thomas.ritter@crownpeak.com>
…oleam00#1521) * fix(docker): resolve Claude binary to glibc variant on Debian image Bun's hoisted linker installs both glibc and musl optional-dep packages for the detected CPU arch. The SDK's resolver picks musl first, which fails to execute on the Debian (glibc) base image — the musl dynamic loader is absent, causing every Claude call to fail. Remove the stale ENV CLAUDE_BIN_PATH pointing to the SDK 0.1.x cli.js path (no longer present in SDK 0.2.x), and add runtime arch detection in docker-entrypoint.sh. The entrypoint maps uname -m output to the correct glibc package suffix (x86_64→linux-x64, aarch64→linux-arm64) and exports CLAUDE_BIN_PATH before exec-ing the server. Users can still override via CLAUDE_BIN_PATH in their .env or docker run -e. Closes coleam00#1519 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): warn on unsupported CPU arch in CLAUDE_BIN_PATH detection Add a *) fallback branch so operators on unsupported architectures (riscv64, ppc64le, etc.) get an explicit warning at startup rather than a silent no-op followed by a cryptic SDK error later. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): verify glibc Claude binary exists before pinning CLAUDE_BIN_PATH Add a file-existence check after the uname -m arch detection so a missing or renamed binary in a future SDK version emits a clear WARN at startup rather than letting the container start cleanly and failing silently on first Claude invocation. Follows the project's Fail Fast principle: surface the problem as early as possible with an actionable message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docker): fail fast on unsupported arch or missing Claude binary The arch-detection block warned and continued in two failure modes — unsupported CPU and missing pinned binary — which left CLAUDE_BIN_PATH unset and silently fell through to the SDK's musl-first resolver, the exact bug this fix targets. Exit non-zero in both cases so startup surfaces a clear error instead of a delayed runtime failure. Also switch the existence check from -f to -x (the next thing we do with the path is execute it) and unset the helper var so it doesn't leak into the child process environment. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Archon <archon@archon.dev> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…oleam00#1482) * chore: update Homebrew formula for v0.3.9 * chore(release-skill): use --help (not version) for Step 1.5 smoke probe (#1359) The pre-flight binary smoke does a bare `bun build --compile` — it deliberately skips `scripts/build-binaries.sh` to stay fast. That means packages/paths/src/bundled-build.ts retains its dev defaults, including BUNDLED_IS_BINARY = false. version.ts branches on BUNDLED_IS_BINARY: when true it returns the embedded string; when false it calls getDevVersion(), which reads package.json at `SCRIPT_DIR/../../../../package.json`. Inside a compiled binary SCRIPT_DIR resolves under `$bunfs/root/`, the walk produces a CWD- relative path that doesn't exist, and the smoke aborts with "Failed to read version: package.json not found" — a false positive. Hit during the 0.3.8 release attempt: the real Pi lazy-load fix was working end-to-end; the smoke test was the only thing failing. Use --help instead. It exercises the same module-init graph (so it still catches the real failure modes the skill lists — Pi package.json init crash, Bun --bytecode bugs, CJS wrapper issues, circular imports under minify) but has no dev/binary branch, so no false positive. Also add a longer comment block explaining why --help is preferred, so this doesn't get "normalized" back to `version` by a future drive-by. * chore(test-release-skill): preserve archon-stable across test cycles The brew path of /test-release runs `brew uninstall` in Phase 5 to leave the system in its pre-test state. For operators using the dual-homebrew pattern (renamed brew binary at `/opt/homebrew/bin/archon-stable` so it coexists with a `bun link` dev `archon`), that uninstall wipes the Cellar dir the `archon-stable` symlink points into → `archon-stable` becomes dangling → `brew cleanup` sweeps it away on the next brew op. Next time the operator wants stable, they have to manually re-run `brew-upgrade-archon`. Fix: make the skill aware of `archon-stable` and restore it transparently. - Phase 2 item 4: detect the `archon-stable` symlink before any brew op; export `ARCHON_STABLE_WAS_INSTALLED=yes` so Phase 5 knows to restore it. Only triggers for the brew path (curl-mac/curl-vps don't touch brew so they leave `archon-stable` alone). - Phase 5 brew path: after `brew uninstall + untap`, if the flag was set, re-tap + re-install + rename. Verifies the restored `archon-stable` reports a version and warns (non-fatal) if the rename target is missing. Documents the tradeoff: the restored version is "whatever the tap ships today", not necessarily the pre-test version — usually that's what the operator wants (the release they just tested becomes stable) but the back-version-QA case requires a manual `brew-upgrade-archon` after. - Phase 1 confirmation banner now mentions that `archon-stable` will be preserved so the operator isn't surprised by the reinstall during Phase 5. No changes to curl-mac/curl-vps paths. No changes to Phase 4 test suite. * fix(providers/pi): install PI_PACKAGE_DIR shim so Pi workflows run in a compiled binary (#1360) v0.3.9 made Pi boot-safe: lazy-loading its imports meant `archon version` no longer crashed on `@mariozechner/pi-coding-agent/dist/config.js`'s module-init `readFileSync(getPackageJsonPath())`. That's what the `provider-lazy-load.test.ts` regression test guards. The fix was only half the problem though. When a Pi workflow actually runs, sendQuery() triggers the dynamic import — and Pi's config.js module-init fires then, hitting the exact same ENOENT on `dirname(process.execPath)/package.json`. Discovered by running `archon workflow run test-pi` against a locally-compiled 0.3.9 binary: [main] Failed: ENOENT: no such file or directory, open '/private/tmp/package.json' at readFileSync (unknown) at <anonymous> (/$bunfs/root/archon-providertest:184:7889) at init_config Boot-safe ≠ runtime-safe. The `/test-release` run for 0.3.9 passed because it only exercised `archon-assist` (Claude); Pi was never actually invoked on the released binary. Fix: before the dynamic `import('@mariozechner/pi-coding-agent')` in sendQuery, install a PI_PACKAGE_DIR shim. Pi's config.js checks `process.env.PI_PACKAGE_DIR` first in its `getPackageDir()` and short-circuits the `dirname(process.execPath)` walk. We write a minimal `{name, version, piConfig:{}}` stub to `tmpdir()/archon-pi-shim/package.json` (idempotent — existsSync check) and set the env var. Pi only reads `piConfig.name`, `piConfig.configDir`, and `version` from that file, all optional, so the stub surface is genuinely minimal. Localized to PiProvider: no global state, no mutation of any shared config, no upstream fork. Claude and Codex providers are unaffected (their SDKs don't have this class of module-init side effect). Verified end-to-end: built a compiled archon binary with this patch, ran `archon workflow run test-pi --no-worktree` (Pi workflow with model `anthropic/claude-haiku-4-5`), got a clean response. Before the patch, same binary crashed at `dag_node_started` with the ENOENT above. Regression test added: asserts `PI_PACKAGE_DIR` is set after sendQuery hits even its fast-fail "no model" path. Together with the existing `provider-lazy-load.test.ts` (boot-safe) this covers both halves. * feat(providers): autodetect canonical binary install paths for Claude and Codex (#1361) Both binary resolvers previously stopped at env-var + explicit config and threw a "not found" error when neither was set. Users who followed the upstream-recommended install flow (Anthropic's `curl install.sh` for Claude, `npm install -g @openai/codex`) still had to manually set either `CLAUDE_BIN_PATH` / `CODEX_BIN_PATH` or the corresponding config field before any workflow could run. Add a tier-N autodetect step between the explicit config tier and the install-instructions throw. Purely additive: env and config still win when set (precedence covered by new tests). On autodetect miss, the same install-instructions error fires as before. Claude probe list (verified against docs.claude.com "Uninstall Claude Code → Native installation" section): - $HOME/.local/bin/claude (mac/linux native installer) - $USERPROFILE\.local\bin\claude.exe (Windows native installer) Codex probe list (verified against openai/codex README; npm global- install puts the binary at `{npm_prefix}/bin/<name>` on POSIX, `{npm_prefix}\<name>.cmd` on Windows): - $HOME/.npm-global/bin/codex (user-set `npm config set prefix`) - /opt/homebrew/bin/codex (mac arm64 with homebrew-node) - /usr/local/bin/codex (mac intel / linux system node) - %APPDATA%\npm\codex.cmd (Windows npm global default) - $HOME\.npm-global\codex.cmd (Windows user-set prefix) Not probed (explicit override still required): - Custom npm prefixes — `npm root -g` would need a subprocess per resolve, too much surface for a probe helper - `brew install --cask codex` — cask layout isn't a PATH binary - Manual GitHub Releases extracts — placement is user-determined - `~/.bun/bin/codex` — not documented in openai/codex README Pi provider intentionally has no equivalent change: the Pi SDK is bundled into the archon binary (no subprocess), so there's no "binary" to resolve. Pi auth lives at `~/.pi/agent/auth.json` which the SDK already finds by default, and the PR A shim (`PI_PACKAGE_DIR`) handles the package-dir case via Pi's own documented escape hatch. E2E verified: removed both config entries from ~/.archon/config.yaml, rebuilt compiled binary, ran `archon workflow run archon-assist` and a Codex workflow. Logs showed `source: 'autodetect'` for both, responses returned cleanly. * fix(providers/test): use os.homedir() instead of $HOME in claude binary autodetect test The native-installer autodetect test computed its expected path from process.env.HOME, but the implementation uses node:os homedir(). On Windows, HOME is typically unset (Windows uses USERPROFILE), so the test fell back to '/Users/test' while the resolver returned the real home dir — making the spy's path-equality check fail and breaking CI on windows-latest. Mirror the implementation by importing homedir() from node:os and joining with node:path so the expected path matches the actual platform-resolved home and separator. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(server): contain Discord login failure so it doesn't kill the server (#1365) Reported in #1365: a user running `archon serve` with DISCORD_BOT_TOKEN set but the "Message Content Intent" toggle disabled in the Discord Developer Portal saw the entire server crash with `Used disallowed intents`. Discord rejects the gateway connection (close code 4014) when a privileged intent is requested without being enabled, and the unguarded `await discord.start()` propagated the error all the way up, taking the web UI down with it. Wrap discord.start() in try/catch — log the failure with an actionable hint (special-cased for the disallowed-intent error) and continue running. Other adapters and the web UI come up regardless. The shutdown handler already uses optional chaining (`discord?.stop()`) so nulling discord after a failed start is safe. Other adapters (Telegram, Slack, GitHub, Gitea, GitLab) have the same unguarded-start pattern but are out of scope for this fix — addressing them is tracked separately. Also expanded the Discord setup docs with a caution callout that names the exact error string and the new log event so users can grep for both. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(script-nodes): dedicated guide + teach the archon skill (#1362) * docs(script-nodes): add dedicated guide and teach the archon skill how to write them Script nodes (script:) have been a first-class DAG node type since v0.3.3 but were documented only as one-liners in CLAUDE.md and a CI smoke test. Claude Code reading the archon skill would see "Four Node Types: command, prompt, bash, loop" and reach for bash+node/python one-liners instead of a proper script node — losing bun's --no-env-file isolation, uv's --with dependency pins, and the .archon/scripts/ reuse story. - New packages/docs-web/src/content/docs/guides/script-nodes.md mirroring the structure of loop-nodes.md / approval-nodes.md: schema, inline vs named dispatch, runtime/deps semantics, scripts directory precedence (repo > home), extension-runtime mapping, env isolation, stdout/stderr contract, patterns, and the explicit list of ignored AI fields. - guides/authoring-workflows.md and guides/index.md updated so the new guide is discoverable from both the node-types table and the guides landing page. - reference/variables.md calls out the no-shell-quote difference between bash: and script: substitution — a subtle correctness trap when adapting a bash pattern into a script node. - Sidebar order bumped +1 on hooks/mcp-servers/skills/global-workflows/ remotion-workflow to slot script-nodes at order 5 next to the other node-type guides. - .claude/skills/archon/SKILL.md: replaces stale "Four Node Types" (which also silently omitted approval and cancel) with the accurate seven, with a script-node code block showing both inline and named patterns. - references/workflow-dag.md: full Script Node section covering dispatch, resolution, deps, stdout contract, and the list of AI-only fields that are ignored; validation-rules list updated. - references/dag-advanced.md and references/variables.md: retry-support line corrected; no-shell-quote note added. - examples/dag-workflow.yaml: added an extract-labels TypeScript script node and updated the header comment. * fix(docs): review follow-ups for script-node guide - skills example: extract-labels was reading process.env.ISSUE_JSON which is never set; use String.raw`$fetch-issue.output` so the upstream bash node's JSON is actually consumed - guides/script-nodes.md + skills/workflow-dag.md: idle_timeout is accepted but ignored on script (and bash) nodes — executeScriptNode only reads node.timeout. Clarify that script/bash use `timeout`, not idle_timeout - archon-workflow-builder.yaml: prompt enumerated only bash/prompt/command/loop, so the AI builder could never propose script or approval nodes. Add both (plus examples + rule about script output not being shell-quoted) and regenerate bundled defaults - book/dag-workflows.md + book/quick-reference.md + adapters/web.md: fill in the node-type references that were missing script, approval, and cancel. adapters/web.md also overclaimed "loop" in the palette — NodePalette.tsx only drags command/prompt/bash, so note that the other kinds are YAML-only * docs/skill: general hardening — fix inaccuracies, fill workflow/CLI/env gaps, add good-practices + troubleshooting (#1363) * fix(skill/when): document the full `when:` operator set and compound expressions The skill reference previously stated "operators: ==, != only" which is materially wrong — the condition evaluator supports ==, !=, <, >, <=, >= plus && / || compound expressions with && binding tighter than ||, plus dot-notation JSON field access. An agent authoring a workflow from the skill would think half the operators don't exist. Replaces the single-sentence section with a structured reference covering: - All six comparison operators (string and numeric modes) - Compound expressions with precedence rules and short-circuit eval - JSON dot notation semantics and failure modes - The fail-closed rules in full (invalid expression, non-numeric side, missing field, skipped upstream) Grounded in packages/workflows/src/condition-evaluator.ts. * feat(skill): document Approval and Cancel node types Approval and cancel nodes are first-class DAG node types (approval since the workflow lifecycle work in #871, cancel as a guarded-exit primitive) but the skill never described either one. An agent reading the skill and asked to "add a review gate before implementation" or "stop the workflow if the input is unsafe" would fall back to bash + exit 1, losing the proper semantics (cancelled vs. failed, on_reject AI rework, web UI auto-resume). Approval node coverage (references/workflow-dag.md, SKILL.md): - Full configuration block with message, capture_response, on_reject - The interactive: true workflow-level requirement for web UI delivery - Approve/reject commands across all platforms (CLI, slash, natural language) and the capture_response → $node-id.output flow - Ignored-fields list + the on_reject.prompt AI sub-node exception Cancel node coverage (references/workflow-dag.md, SKILL.md): - Single-field schema (cancel: "<reason>") - Lifecycle: cancelled (not failed); in-flight parallel nodes stopped; no DAG auto-resume path - The "cancel: vs bash-exit-1" decision rule (expected precondition miss vs. check itself failing) - Two canonical patterns — upstream-classification gate, pre-expensive-step gate Validation-rules list updated to enumerate approval/cancel constraints (message non-empty, on_reject.max_attempts range 1-10, cancel reason non-empty), plus a forward note that script: joins the mutually-exclusive set once PR #1362 lands. Placement in both files is after the Loop section and before the validation section, so this commit stays additive with respect to PR #1362's Script node insertion between Bash and Loop — rebase is clean. * feat(skill): document workflow-level fields beyond name/provider/model The skill's Schema section previously showed only name, description, provider, and model at the workflow level — which is most of a stub. Agents asked to "use the 1M-context Claude beta" or "run this under a network sandbox" or "add a fallback model in case Opus rate-limits" had no way to discover that any of these fields existed at the workflow level. Adds a comprehensive Workflow-Level Fields section covering: - Core: name, description, provider, model, interactive (with explicit callout that interactive: true is REQUIRED for approval/loop gates on web UI — a common footgun) - Isolation: worktree.enabled for pin-on/pin-off (the only worktree field at workflow level; baseBranch/copyFiles/path/initSubmodules are config.yaml only, so a cross-reference points there) - Claude SDK advanced: effort, thinking, fallbackModel, betas, sandbox, with explicit per-node-only exceptions (maxBudgetUsd, systemPrompt) - Codex-specific: modelReasoningEffort (with note that it's NOT the same as Claude's effort — this has confused users), webSearchMode, additionalDirectories - A complete worked example combining sandbox + approval + interactive All fields cross-referenced against packages/workflows/src/schemas/workflow.ts and packages/workflows/src/schemas/dag-node.ts. * feat(skill/loop): document interactive loops and gate_message Interactive loop nodes pause between iterations for human feedback via /workflow approve — used by archon-piv-loop and archon-interactive-prd. The skill's Loop Nodes section previously omitted both interactive: true and gate_message entirely, so an agent writing a guided-refinement workflow wouldn't know the feature exists or that gate_message is required at parse time. Adds: - interactive and gate_message rows to the config table (marking gate_message as required when interactive: true — enforced by the loader's superRefine) - A dedicated "Interactive Loops" subsection explaining the 6-step iterate-pause-approve-resume flow - Explicit call-out that $LOOP_USER_INPUT populates ONLY on the first iteration of a resumed session — easy to miss and a common surprise - Workflow-level interactive: true requirement for web UI delivery (loader warning otherwise) so the full-flow example is complete - Note that until_bash substitution DOES shell-quote $nodeId.output (unlike script bodies) — called out since the audit surfaced this inconsistency * fix(skill/cli): complete the CLI command reference with missing lifecycle commands The CLI reference previously documented only list, run, cleanup, validate, complete, version, setup, and chat — missing nearly every workflow lifecycle command an agent needs to operate a paused, failed, or stuck run. The interactive-workflows reference assumed these commands existed without actually documenting them. Adds full documentation for: - archon workflow status — show running workflow(s) - archon workflow approve <run-id> [comment] — resume approval gate (also populates $LOOP_USER_INPUT on interactive loops and the gate node's output when capture_response: true) - archon workflow reject <run-id> [reason] — reject gate; cancels or triggers on_reject rework depending on node config - archon workflow cancel <run-id> — terminate running/paused with in-flight subprocess kill - archon workflow abandon <run-id> — mark stuck row cancelled without subprocess kill (for orphan-cleanup after server crashes — matches the #1216 precedent) - archon workflow resume <run-id> [message] — force-resume specific run (auto-resume is default; this is for explicit override) - archon workflow cleanup [days] — disk hygiene for old terminal runs (with explicit callout that it does NOT transition 'running' rows, a common confusion) - archon workflow event emit — used inside loop prompts for state signalling; documented so agents don't invent their own mechanism - archon continue <branch> [flags] [msg] — iterative-session entry point with --workflow and --no-context flags Also: - Adds --allow-env-keys flag to the `workflow run` flag table with audit-log context and the env-leak-gate remediation use case - Adds an "Auto-resume without --resume" note disambiguating when --resume is needed vs. when auto-resume handles it - Adds --include-closed flag to `isolation cleanup`, which was previously missing; converts the flag list to a structured table - Explains the cancel/abandon distinction (live subprocess vs. orphan) All grounded in packages/cli/src/commands/workflow.ts, continue.ts, and isolation.ts. * feat(skill/repo-init): add scripts/ and state/, three-path env model, per-project env injection The repo-init reference was missing two first-class .archon/ directories (scripts/ since v0.3.3, state/ since the workflow-state feature) and had nothing to say about env — the #1 thing a user hits on first-run when their repo has a .env file with API keys. Directory tree updates: - Adds .archon/scripts/ with the extension->runtime rule (.ts/.js -> bun, .py -> uv) so agents know where to put named scripts referenced by script: nodes. - Adds .archon/state/ with explicit "always gitignore" callout — these are runtime artifacts, not source. Previously undocumented in the skill. - Adds .archon/.env (repo-scoped Archon env) and distinguishes it from the target repo's top-level .env. - Adds a "What each directory is for" list so the structure isn't just a tree with no narrative. .gitignore guidance: - state/ and .env added as must-gitignore (state/ matches CLAUDE.md and reference/archon-directories.md — skill was lagging). - mcp/ demoted to conditional — gitignore only if you hardcode secrets. New "Three-Path Env Model" section: - ~/.archon/.env (trusted, user), <cwd>/.archon/.env (trusted, repo), <cwd>/.env (UNTRUSTED, target project — stripped from subprocess env). - Precedence (override: true across archon-owned paths) and the observable [archon] loaded N keys / stripped K keys log lines so operators can verify what actually happened. - Decision tree for where to put API keys vs. target-project env vs. things Archon shouldn't touch. - Links to archon setup --scope home|project with --force for writing to the right file with timestamped backups. New "Per-Project Env Injection" section: - Documents both managed surfaces: .archon/config.yaml env: block (git-committed, $REF expansion) and Web UI Settings → Projects → Env Vars (DB-stored, never returned over API). - Names every execution surface that receives the injected vars: Claude/Codex/Pi subprocess, bash: nodes, script: nodes, and direct codebase-scoped chat. - Documents the env-leak gate with all 5 remediation paths so an agent hitting "Cannot register: env has sensitive keys" knows the options. Grounded in CHANGELOG v0.3.7 (three-path env + setup flags), v0.3.0 (env-leak gate), and reference/security.md on the docs site. * fix(skill/authoring-commands): correct override paths and add home-scoped commands The file-location and discovery sections described an override layout that does not match the actual resolver. It showed: .archon/commands/defaults/archon-assist.md # Overrides the bundled and claimed `.archon/commands/defaults/` was where repo-level overrides lived. In fact the resolver (executor-shared.ts:152-200 + command- validation.ts) walks `.archon/commands/` 1 level deep and uses basename matching — putting `archon-assist.md` at the top of `.archon/commands/` is the canonical way to override the bundled version. The `defaults/` subfolder is a Archon-internal convention for shipping bundled defaults, not a user-facing override pattern. Also, home-scoped commands (`~/.archon/commands/`, shipped in v0.3.7) were completely absent — agents authoring personal helpers wouldn't know they could live at the user level and be shared across every repo. Changes: - File Location section now shows all three discovery scopes (repo, home, bundled) with precedence ordering and 1-level subfolder rules - Duplicate-basename rule documented as a user error surface - Discovery and Priority section rewritten with accurate 3-step lookup order — no more references to the nonexistent defaults/ override path - Adds the Web UI "Global (~/.archon/commands/)" palette label note so users authoring helpers for the builder know what to expect No code changes — this is a pure fix of stale/incorrect skill reference material. * feat(skill): add workflow good-practices and troubleshooting reference pages Closes two gaps from the audit. The skill previously had zero guidance on designing multi-node workflows (what to avoid, what to reach for first, how to structure artifact chains) and zero guidance on where to look when things go wrong (log paths, env-leak gate remediations, orphan-row cleanup, resume semantics). New references/good-practices.md (9 Good Practices + 7 Anti-Patterns): - Use deterministic nodes (bash:/script:) for deterministic work, AI for reasoning — the single biggest quality lever - output_format required whenever downstream when: reads a field — the most common source of "workflow silently routes wrong" - trigger_rule: none_failed_min_one_success after conditional branches — the classic bug where all_success fails because a skipped when:-gated branch doesn't count as a success - context: fresh requires artifacts for state passing — commands must explicitly "read $ARTIFACTS_DIR/..." when downstream of fresh - Cheap models (haiku) for glue, strong for substance - Workflow descriptions as routing affordances - Validate (archon validate workflows) + smoke-run before shipping - Artifact-chain-first design - worktree.enabled: true for code-changing workflows (reversibility) - Anti-patterns with before/after YAML examples for each (AI-for-tests, free-form when: matching, context: fresh without artifacts, long flat AI-node layers, secrets in YAML, retry on loop nodes, tiny max_iterations, missing workflow-level interactive:, tool-restricted MCP nodes) New references/troubleshooting.md: - Log location (~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl) with jq recipes for common queries (last assistant message, failed events, full stream) - Artifact location for cross-node handoff debugging - 9 Common Failure Modes, each with root cause + concrete fix: - $BASE_BRANCH unresolvable - Env-leak gate (5 remediations) - Claude/Codex binary not found (compiled-binary-only) - "running" forever (AI working / orphan / idle_timeout) - Mid-workflow failure and auto-resume semantics - Approval gate missing on web UI (workflow-level interactive:) - MCP plugin connection noise (filtered by design) - Empty $nodeId.output / field access (4 causes) - Diagnostic command cheat sheet (list, status, isolation list, validate, tail-log, --verbose, LOG_LEVEL=debug) - Escalation protocol (version + validate + log tail + CHANGELOG + issue) SKILL.md routing table now dispatches "Workflow good practices / anti-patterns" and "Troubleshoot a failing / stuck workflow" to the new references so an agent can find them without having to know they exist. * docs(book): update node-types coverage from four to all seven The book is the curated first-contact reading path (landing page → "Get Started" → /book/). Both dag-workflows.md and quick-reference.md were stuck on "four node types" — missing script, approval, and cancel. A user reading the book as their first introduction would form an incomplete mental model, then find three more node types in the reference section later with no explanation of when they arrived. book/dag-workflows.md: - "four node types" → "seven node types. Exactly one mode field is required per node" - Table now lists Command, Prompt, Bash, Script, Loop, Approval, Cancel with one-line "when to use" for each, and cross-links to the dedicated guide pages for Script / Loop / Approval - New sections below the table for Script (inline + named examples with runtime and deps), Approval (with the interactive: true workflow-level note that's easy to miss), and Cancel (guarded-exit pattern) — keeping the existing narrative shape for Bash and Loop book/quick-reference.md: - Node Options table now includes script, approval, cancel rows - agents row added (inline sub-agents, Claude-only) - New "Script-specific fields" and "Approval-specific fields" subsections so the cheat-sheet is actually complete rather than pointing users elsewhere for the required constraints - Retry row callout that loop nodes hard-error on retry — previously omitted - bash timeout note widened to cover script timeout (same semantics) Both files are docs-web content; the CI build on the docs-script-nodes PR (#1362) previously validated the Starlight build path with a similar table addition, so this should render clean. * fix(skill/cli): remove nonexistent \`archon workflow cancel\`, fix workflow status jq recipe Two accuracy issues from the PR code-reviewer (comment 4311243858). C1: \`archon workflow cancel <run-id>\` does NOT exist as a CLI subcommand. The switch at packages/cli/src/cli.ts:318-485 dispatches on list / run / status / resume / abandon / approve / reject / cleanup / event — running \`archon workflow cancel\` hits the default case and exits with "Unknown workflow subcommand: cancel" (cli.ts:478-484). Active cancellation is only available via: - /workflow cancel <run-id> chat slash command (all platforms) - Cancel button on the Web UI dashboard - POST /api/workflows/runs/{runId}/cancel REST endpoint cli-commands.md: removed the \`### archon workflow cancel <run-id>\` subsection; kept the \`abandon\` subsection but made it explicit that abandon does NOT kill a subprocess. Added a call-out box at the bottom of the abandon section explaining where to go for actual cancellation. troubleshooting.md "running forever" section: split the original cancel-vs-abandon advice into three bullets — Web UI / CLI abandon (for orphans, no subprocess kill) / chat \`/workflow cancel\` (for live runs that need interruption). Added an explicit "there is no archon workflow cancel CLI subcommand" parenthetical since the wrong command was being suggested in flow. I1: the \`archon workflow list --json\` diagnostic used an incorrect jq filter. workflow list's --json output (workflow.ts:185-219) has shape { workflows: [{ name, description, provider?, model?, ... }], errors: [...] } with no \`runs\` field — \`jq '.workflows[] | select(.runs)'\` returns empty unconditionally. Replaced with \`archon workflow status --json | jq '.runs[]'\`, which matches the actual shape of workflowStatusCommand at workflow.ts:852+ ({ runs: WorkflowRun[] }). Also tightened the narration to distinguish JSON from human-readable status output. No change to the commit history in this PR — these are follow-up fixes to claims I introduced in earlier commits of this branch (f10b989e for C1, 66d2b86e for I1). * fix(skill): remove env-leak gate references (feature was removed in provider extraction) C2 from the PR code-reviewer (comment 4311243858). The pre-spawn env-leak gate was removed from the codebase during the provider-extraction refactor — see TODO(#1135) at packages/providers/src/claude/provider.ts:908. Zero hits for --allow-env-keys / allowEnvKeys / allow_env_keys / allow_target_repo_keys across packages/. The CLI's parseArgs (cli.ts:182-208) has no --allow-env-keys option, and because parseArgs uses strict: false, an unknown --allow-env-keys would be silently ignored rather than error. What remains accurate and is NOT touched: - Three-Path Env Model section (user/repo archon-owned envs are loaded; target repo <cwd>/.env keys are stripped from process.env at boot) still correctly describes current behavior, grounded in packages/paths/src/strip-cwd-env.ts + env-integration.test.ts - Per-Project Env Injection section (Option 1: .archon/config.yaml env: block; Option 2: Web UI Settings → Projects → Env Vars) is unchanged — both remain the sanctioned way to get env vars into subprocesses Removed claims (all three files): - cli-commands.md: --allow-env-keys flag row in the workflow run flags table - repo-init.md: the "Env-leak gate" subsection at the end of Per-Project Env Injection listing 5 remediations (all of which reference UI/CLI/ config surfaces that don't exist). Replaced with a succinct callout that explains the actual current behavior — target repo .env keys are stripped, workflows that need those values should use managed injection — so the reader still gets the "where to put my env vars" answer - troubleshooting.md: the "Cannot register: codebase has sensitive env keys" section (error message that can no longer be emitted) If the env-leak gate is ever resurrected per TODO(#1135), the docs can be re-added then. The CHANGELOG v0.3.0 entry describing the gate is a historical record of past behavior and does not need to be rewritten. * fix(skill/troubleshooting): correct JSONL event type names and field name C3 from the PR code-reviewer (comment 4311243858). The troubleshooting reference's event-types table used _started / _completed / _failed suffixes, but packages/workflows/src/logger.ts:19-30 shows the actual WorkflowEvent.type enum is: workflow_start | workflow_complete | workflow_error | assistant | tool | validation | node_start | node_complete | node_skipped | node_error The second jq recipe also queried `.event` but the discriminator is `.type`. Fixes: - Event table: renamed columns (_started → _start, _completed → _complete, _failed → _error). Explicitly called out the field name as `type` so the reader knows what jq selector to use - Replaced the "tool_use / tool_result" row with a single `tool` row and listed its actual payload fields (tool_name, tool_input, duration_ms, tokens) — tool_use/tool_result are SDK message kinds that appear within the AI stream, not top-level log event types - Added a `validation` row (was missing; it's emitted by workflow-level validation calls with `check` and `result` fields) - Removed `retry_attempt` row — this event type is not emitted to the JSONL file. Retry bookkeeping goes through pino logs, not the workflow log file - Added an explicit callout that loop_iteration_started / loop_iteration_completed (and other emitter-only events) go through the workflow event emitter + DB workflow_events table, NOT the JSONL file. Pointed readers to the DB or Web UI for loop-level detail. This distinguishes the two parallel event systems — easy to conflate (store.ts:11-17 uses _started/_completed/_failed for the DB side, logger.ts uses _start/_complete/_error for JSONL) - Fixed the "all failed events" jq recipe: .event → .type and _failed → _error - Minor cleanup: the inline "tool_use events" mention in the "running forever" section said the wrong event name — updated to "tool or assistant events in the tail" Grounded in packages/workflows/src/logger.ts (canonical JSONL event shape) and packages/workflows/src/store.ts (the parallel DB event naming, which the reviewer correctly flagged as different and worth keeping distinct). * fix(skill): two stragglers from the code-reviewer audit Cleanup of two references that slipped through the earlier C1 and C3 fixes: - references/troubleshooting.md:126: \`node_failed\` → \`node_error\` (the "Node output is empty" diagnostics section references the JSONL log, which uses the logger.ts enum — not the DB workflow_events table which does use \`node_failed\`). The C3 fix corrected the event table and one jq recipe but missed this inline mention. - references/interactive-workflows.md:106: removed \`archon workflow cancel <run-id>\` (nonexistent CLI subcommand) from the troubleshooting bullet. This was pre-existing before the hardening PR but fell within the C1 remediation scope. Replaced with the correct triage: reject (approval gate only) vs abandon (orphan cleanup, no subprocess kill) vs chat /workflow cancel (actual subprocess termination). Grounded in the same sources as the earlier C1/C3 commits: packages/cli/src/cli.ts:318-485 (no cancel case) and packages/workflows/src/logger.ts:19-30 (JSONL type enum). * feat(skill): point to archon.diy as the canonical docs source The skill had no reference to archon.diy (the live docs site built from packages/docs-web/). Several reference files said "see the docs site" without naming the URL, leaving the agent to guess or grep the repo for the hostname. An agent with the skill loaded should know that when the distilled reference pages don't cover a case, the full canonical docs are one WebFetch away. SKILL.md: new "Richer Context: archon.diy" section between Routing and Running Workflows. Covers: - When to reach for the live docs (longer examples, tutorial framing, features the skill only mentions in passing, "where's that documented?" user questions) - URL map — 13 starting points covering getting-started, book (tutorial series), guides/ (authoring + per-node-type + per-node-feature), reference/ (variables, CLI, security, architecture, configuration, troubleshooting), adapters/, deployment/ - Precedence: skill refs first (context-cheap, tuned for agents), docs site as escalation. Prevents agents defaulting to WebFetch when a local skill ref already covers the answer Also upgrades the 5 existing generic "docs site" mentions across reference files to concrete archon.diy URLs with anchor fragments where helpful: - good-practices.md: Inline sub-agents pattern → archon.diy/guides/ authoring-workflows/#inline-sub-agents - troubleshooting.md: "Install page on the docs site" → archon.diy/ getting-started/installation/ - workflow-dag.md: "Workflow Description Best Practices" → anchor link; sandbox schema reference → archon.diy/guides/authoring-workflows/ #claude-sdk-advanced-options - repo-init.md: Security Model reference → archon.diy/reference/ security/#target-repo-env-isolation (deep-link into the section that covers the <cwd>/.env strip behavior) URL source of truth: astro.config.mjs:5 (site: 'https://archon.diy'). URL structure mirrors packages/docs-web/src/content/docs/<section>/ <page>.md — verified by the 62 pages the docs build produces. * chore(workflows): switch default Opus pin to opus[1m] alias (#1395) Anthropic's Opus 4.7 landed 2026-04-16; on the Anthropic API, opus / opus[1m] now resolve to 4.7 with a 1M context window at standard pricing. Using the alias instead of the hard-pinned claude-opus-4-6[1m] lets bundled default workflows auto-track the recommended Opus version. No explicit effort is set, so nodes inherit the per-model default (xhigh on 4.7, high on 4.6). * fix(workflow): migrate piv-loop plan handoff to $ARTIFACTS_DIR (#1398) * fix(workflow): migrate piv-loop plan handoff to $ARTIFACTS_DIR (#1380) The create-plan node used a relative path (.claude/archon/plans/{slug}.plan.md) that the AI agent would sometimes write to a different location, breaking all downstream nodes that glob for the plan file. Migrated all plan/progress file references to $ARTIFACTS_DIR/plan.md and $ARTIFACTS_DIR/progress.txt, matching the pattern used by archon-fix-github-issue and other workflows. Changes: - Replace slug-based plan path with $ARTIFACTS_DIR/plan.md in create-plan node - Replace ls -t glob discovery with direct $ARTIFACTS_DIR/plan.md reads in refine-plan, code-review, and fix-feedback nodes - Replace empty-string guard with file-existence check in implement-setup bash - Migrate progress.txt references in implement loop to $ARTIFACTS_DIR/ - Add explicit plan/progress paths in finalize node - Regenerated bundled-defaults.generated.ts Fixes #1380 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(workflow): address review findings in archon-piv-loop - Rename 'Step 2: Write the Plan' to 'Step 2: Plan File Location' to eliminate the duplicate heading that collided with Step 3's identical title in the create-plan node - Guard implement-setup against a 0-task plan file: exit 1 with a clear error when no '### Task N:' sections are found, preventing a silent no-op implement loop - Remove 2>/dev/null from code-review commit so pre-commit hook failures and other stderr are visible to the agent instead of silently swallowed - Replace '|| true' on git push in finalize with an explicit WARNING echo so push failures (auth, upstream conflict, no remote) surface to the agent rather than being silently ignored - Regenerate bundled-defaults.generated.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(workflows): regenerate bundled defaults to match opus[1m] alias The bundle was stale relative to the YAML sources after #1395 merged — check:bundled was failing CI. Regenerated; no YAML edits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(workflows): add anyFailed status derivation coverage for DAG executor (#1403) PIV Task 1: Adds three new tests in a dedicated describe block 'executeDagWorkflow -- final status derivation' covering the anyFailed branch (dag-executor.ts ~line 2956) that previously had no direct test: - one success + one independent failure calls failWorkflowRun (not completeWorkflowRun) - multiple successes + one failure calls failWorkflowRun (not completeWorkflowRun) - trigger_rule: none_failed skips dependent node but anyFailed still marks run failed Fixes #1381. * docs/skill: add parameter-matrix.md quick-lookup reference New reference for the archon skill: a single-glance lookup of which parameter works on which node type, an intent-based "how do I..." table, a consolidated silent-failure catalog, and an inline agents: section (previously only referenced via archon.diy). Purpose is complementary, not duplicative: - workflow-dag.md remains the authoring guide - dag-advanced.md remains the hooks/MCP/skills/retry deep-dive - good-practices.md remains the patterns and anti-patterns - parameter-matrix.md is the grep-this-first lookup when you know the outcome you want but not which field gets you there Also registers the new reference in SKILL.md routing table. * docs: point contributors at PR template and Closes #N convention Add explicit references to .github/PULL_REQUEST_TEMPLATE.md in both CONTRIBUTING.md and CLAUDE.md, plus a reminder to link issues with Closes/Fixes/Resolves so they auto-close on merge. Repo-triage runs were flagging dozens of partially-filled or unlinked PRs each cycle. * feat(workflows): add maintainer-standup workflow for daily PR/issue triage (#1428) * feat(workflows): add maintainer-standup workflow for daily PR/issue triage Daily morning briefing that pulls origin/dev, triages all open PRs and assigned issues against direction.md, and surfaces progress vs. the previous run. Designed for live-checkout use (worktree.enabled: false) so it can read its own state. Layout under .archon/maintainer-standup/: - direction.md (committed) — project north-star: what Archon IS / IS NOT. Drives PR P4 polite-decline classification with cited clauses. - README.md / profile.md.example — setup docs and template for new maintainers. - profile.md, state.json, briefs/YYYY-MM-DD.md — gitignored, per-maintainer. Engine: - 3 parallel gather scripts in .archon/scripts/maintainer-standup-*.ts (git-status, gh-data, read-context) — bun runtime, JSON stdout. - Synthesis node: command file with output_format schema for { brief_markdown, next_state }. - Persist node: tiny inline bun script writes both to disk. Run-to-run continuity: state.json carries observed_prs/issues snapshots, so the next run can detect what merged, what closed, what the maintainer shipped, and which carry-over items aged past N days. Also adds .archon/** to the ESLint global ignore list (matches the existing .claude/skills/** pattern) since .archon/ is user content and not part of any tsconfig project. * fix(maintainer-standup): address CodeRabbit review on #1428 - gh-data: bump --limit 100 → 1000 on all_open_prs and warn loudly when the cap is hit; preserves the observed_prs invariant the next-run "resolved since last run" diff depends on. (CodeRabbit critical) - maintainer-standup.md: clarify P1 CI signal — the gathered payload only carries mergeStateStatus, not statusCheckRollup; for borderline P1s, drill in via `gh pr checks <n>`. (CodeRabbit minor) - workflow.yaml persist: write briefs under local YYYY-MM-DD (sv-SE locale) instead of UTC ISO date, so an evening run doesn't file tomorrow's brief and break recent_briefs lookups. (CodeRabbit minor) - workflow.yaml persist: wrap state/brief writes in try/catch; on failure dump brief_markdown and next_state to stderr so a 5-minute Sonnet synthesis isn't lost to a transient disk error. (CodeRabbit minor) - gh-data + git-status: switch from execSync (shell-string) to execFileSync (argv array) for git/gh invocations. Defense-in-depth against shell metacharacters in values that pass through (esp. the gh_handle from profile.md). (CodeRabbit nitpick) * feat(workflows): support explicit tags in workflow YAML (#1190) Add optional `tags: string[]` to `workflowBaseSchema`. Explicit values take precedence over keyword inference; `tags: []` suppresses inference end-to-end; omitting the field falls back to inference (backwards compatible). Non-array values warn-and-ignore matching the sibling `worktree`/`additionalDirectories` patterns. * feat(workflows): add maintainer-review-pr and group maintainer workflows under maintainer/ (#1430) * feat(workflows): add maintainer-review-pr and group maintainer workflows under .archon/workflows/maintainer/ Adds the maintainer-review-pr workflow — a Pi/Minimax-based PR triage flow that gates on direction alignment, scope focus, and PR-template quality before doing any deep review. If the gate clears, runs the five review aspects (code/error-handling/test-coverage/comment-quality/ docs-impact) as parallel Archon nodes and auto-posts a synthesized review comment. If the gate fails (direction conflict, multiple concerns, sprawling scope), drafts a polite-decline comment and pauses for the maintainer's approval before posting. Reorganizes the existing maintainer-standup workflow into the same subfolder so all maintainer-facing workflows live together. Subfolder grouping is supported by the workflow loader (1 level deep, resolution by filename). What lands: - .archon/workflows/maintainer/maintainer-standup.yaml (moved from .archon/workflows/maintainer-standup.yaml) - .archon/workflows/maintainer/maintainer-review-pr.yaml (new) - .archon/commands/maintainer-review-{gate,code-review,error-handling, test-coverage,comment-quality,docs-impact,synthesize,report}.md (new, Pi-tuned variants of the existing review-agent commands so they avoid Claude-only Task / sub-agent patterns) Pi/Minimax integration: - Uses provider: pi, model: minimax/MiniMax-M2.7 — verified via the e2e-minimax-smoke test that Pi correctly routes to Minimax (session jsonl confirms provider=minimax) and that Pi's best-effort output_format parser handles the gate's nested schema. - Two test runs landed real comments: a direction-decline on PR #1335 and a deep-review on PR #1369. Both were posted to GitHub via the workflow's gh pr comment node. * chore(workflows): also group repo-triage under .archon/workflows/maintainer/ repo-triage is the third maintainer-facing workflow alongside maintainer-standup and maintainer-review-pr; group it in the same subfolder for consistency. Subfolder resolution is by filename so the workflow name is unchanged. * feat(pi): use ModelRegistry to support custom models and skip auth for unmapped providers (#1284) Closes #1096. - Switch Pi provider model lookup from pi-ai's getModel() (static catalog only) to ModelRegistry.create(authStorage).find() so user-configured custom models in ~/.pi/agent/models.json (LM Studio, ollama, llamacpp, custom OpenAI-compatible endpoints) are discoverable. - Remove the local lookupPiModel helper. - For env-var-mapped providers (anthropic, openai, etc.) still throw with a pi /login hint when credentials are missing. For unmapped providers, log pi.auth_missing at info and continue so local models that don't need credentials work without ceremony. - Surface modelRegistry.getError() in the not-found message and emit pi.model_not_found so users debugging custom-provider configs see the real cause (e.g. missing baseUrl in models.json). - Guard AuthStorage.create() and ModelRegistry.create() with try/catch so a malformed ~/.pi/agent/auth.json surfaces with Pi-framed context instead of a raw SDK stack trace. - Document the credential-free path for local providers in ai-assistants.md. Co-authored-by: Matt Chapman <Matt@NinjitsuWeb.com> * chore(workflows): group smoke-test workflows under test-workflows/ + add e2e-minimax-smoke (#1431) * chore(workflows): group all smoke-test workflows under .archon/workflows/test-workflows/ Move the 7 existing e2e-*.yaml smoke tests plus the new e2e-minimax-smoke test into a dedicated subfolder. Subfolder grouping is supported by the workflow loader (1 level deep, resolution by filename) so workflow names are unchanged. Mirrors the .archon/workflows/maintainer/ split landing in #1430. Also adds e2e-minimax-smoke.yaml — a sanity check that Pi correctly routes to Minimax M2.7 via the user's local pi auth, and that Pi's best-effort output_format parser handles a small nested schema. Asserts routing by reading the most recent Pi session jsonl rather than asking the model to self-identify (LLMs are unreliable narrators about their own identity, especially when Pi's system prompt mentions other providers as defaults). * fix(e2e-minimax-smoke): address CodeRabbit review on #1431 - Widen find window from -mmin -3 to -mmin -10. The smoke's three Pi nodes plus the assert can collectively run several minutes on slow networks; 3 minutes was tight enough to false-FAIL on a healthy run. (CodeRabbit minor) - Drop non-deterministic `head -1` over `find` output. find doesn't guarantee any order; on a tie, the wrong file would be picked. Now iterates all matching sessions and breaks on first one carrying the routing signal — any match is sufficient evidence. (CodeRabbit minor) - Replace single-regex `'"provider":"minimax".*"modelId":"MiniMax-M2.7"'` with two separate greps joined by `&&`. JSON field order isn't part of Pi's contract; a future Pi release reordering `provider` and `modelId` in the model_change event would silently false-FAIL the original pattern. The new check is order-independent. (CodeRabbit major) * fix(maintainer-review): address CodeRabbit findings on #1430 (#1432) Six findings, two majors and four minors/nitpicks: - gate.md L17 vs L77: resolved conflicting input-source instructions. Body claimed "all inline, no extra fetch" while a later phase permitted reading PULL_REQUEST_TEMPLATE.md. Now: explicit "one allowed extra read" callout in Phase 1 + matching wording in Gate C. (CodeRabbit major) - gate.md fenced blocks: added missing language identifiers (text/json/ markdown) to satisfy markdownlint MD040. (CodeRabbit minor) - gate.md L155 + read-context.ts: deterministic clock. The 3-day deadline was anchored to prior_state.last_run_at, which can be stale and produce past-dated deadlines. Moved both today and deadline_3d into the read-context.ts output (computed via sv-SE locale → ISO date in local time) and instructed the gate to use $read-context.output.deadline_3d directly. LLMs are unreliable at calendar arithmetic; this avoids it entirely. (CodeRabbit major) - maintainer-review-pr.yaml fetch-diff: dropped 2>/dev/null on gh pr diff so auth / network / deleted-PR failures fail the node instead of feeding an empty diff to the gate. Empty-but-successful diff (PR has no changes) is now an explicit marker the gate can detect. (CodeRabbit minor) - maintainer-review-pr.yaml approve-unclear: added capture_response: true so the maintainer's approve comment flows to the report node. Reject reasoning is already captured by Archon's run record. (CodeRabbit minor) - maintainer-review-pr.yaml post-decline + report.md: the gh pr edit --add-label call previously swallowed all errors with || true and the report still claimed the label was applied. Now writes applied/skipped to $ARTIFACTS_DIR/.label-applied + the gh stderr to .label-error so the report can describe the actual outcome. (CodeRabbit nitpick) * fix(workflows): approval gate bypass after reject-with-redraft on resume (#1435) * fix(workflows): approval gate bypass after reject-with-redraft on resume When an approval node was rejected with on_reject.prompt, the synthetic PromptNode built to run the on_reject prompt reused the approval gate's own node ID. executeNodeInternal then wrote a node_completed event with that ID, causing getCompletedDagNodeOutputs to treat the gate as already completed on the next resume — bypassing the human gate entirely. Fix: give the synthetic node the ID `${node.id}:on_reject` so its node_completed event has a distinct step_name that won't match the approval gate slot in priorCompletedNodes. Adds a regression test asserting no node_completed event with the approval gate's ID is written during on_reject execution. Fixes #1429 * test(workflows): add positive assertion and SSE side-effect comment for on_reject synthetic node Add complementary positive assertion to the regression test to verify that node_completed is written exactly once with step_name 'review:on_reject', ensuring future refactors that suppress the event entirely would be caught. Add inline comment in executeApprovalNode documenting the known SSE side-effect: node_started/node_completed events with nodeId='review:on_reject' flow through the SSE pipeline into the web UI, resulting in a transient phantom node in the execution view. This is cosmetic-only — the human gate contract is preserved. * simplify: reduce duplicate cast pattern in on_reject test assertions * feat(workflows): add mutates_checkout to allow concurrent runs on live checkout (#1438) * feat(workflows): add mutates_checkout field to skip path-lock for concurrent runs Add `mutates_checkout: boolean` (optional, default true) to the workflow schema. When set to false, the executor skips the path-exclusive lock that serializes all runs on the same working path, allowing N concurrent runs on the same live checkout. The primary use case is `maintainer-review-pr`, which reads shared state but writes only to per-run artifact paths and GitHub PR comments — two parallel reviews of different PRs should not fail with "Workflow already active on this path". Changes: - `schemas/workflow.ts`: add optional `mutates_checkout` field - `loader.ts`: parse and propagate the field (warn-and-ignore on invalid values) - `executor.ts`: wrap path-lock guard in `if (workflow.mutates_checkout !== false)` - `executor.test.ts`: two new tests in the concurrent-run guard suite - `maintainer-review-pr.yaml`: opt in with `mutates_checkout: false` * test(workflows): add loader tests for mutates_checkout parsing - Add 5 tests covering false, true, omitted, and invalid (string "yes") values - Invalid non-boolean values are silently dropped with warn — now explicitly tested - Remove the // end mutates_checkout guard trailing comment (no precedent in file) - Clarify loader comment: "parse/warn pattern" not "warn-and-ignore pattern" to avoid implying the return style matches interactive * simplify: collapse nodeType/aiFields pair into single nonAiNode object in parseDagNode * docs: replace String.raw with direct assignment in script node examples (#1434) * docs: replace String.raw with direct assignment in script node examples String.raw`$nodeId.output` fails silently when substituted output contains a backtick, terminating the template literal early and producing cryptic parse errors. JSON is valid JS expression syntax, so direct assignment is safe for all valid JSON values including those with backticks. - Replace String.raw pattern in dag-workflow.yaml example - Replace String.raw pattern in archon-workflow-builder.yaml template - Add CAUTION bullet in workflow-dag.md Script Node section - Add Silent Failures item #14 in parameter-matrix.md - Add Starlight caution aside in script-nodes.md - Extend script bodies bullet in variables.md - Regenerate bundled-defaults.generated.ts Fixes #1427 * docs: fix Rule 6 in generate-yaml prompt to distinguish bun vs uv patterns Rule 6 still referenced JSON.parse after the example was updated to direct assignment, creating a contradiction for the AI code generator. Update the prose to explicitly distinguish TypeScript/bun (direct assignment) from Python/uv (json.loads), matching the updated embedded example. * chore(workflows): group experimental workflows under .archon/workflows/experimental/ Move two repo-scoped workflows that were sitting untracked at the workflow root into a dedicated subfolder. Subfolder grouping is supported by the loader (1 level deep, resolution by filename), so workflow names are unchanged and the /release skill still resolves archon-release correctly. Files moved: - archon-fix-github-issue-experimental.yaml — Path-A variant of the issue-fix workflow used today to land #1434, #1435, #1438. - archon-release.yaml — the live release workflow used by the /release skill end-to-end (validate -> binary smoke -> version bump -> changelog -> approval -> commit -> PR -> tag -> Homebrew formula update). * fix(workflows): export ARTIFACTS_DIR, LOG_DIR, BASE_BRANCH to bash nodes (#1387) executeBashNode previously only merged explicit envVars on top of process.env. The three well-known workflow directories (artifactsDir, logDir, baseBranch) were passed as function parameters and used for compile-time substitution of $ARTIFACTS_DIR / $LOG_DIR / $BASE_BRANCH in the script body, but were never added to the subprocess environment. As a result, any script that relied on shell-runtime expansion — e.g. JSON_FILE="${ARTIFACTS_DIR}/foo.output.json" inside a heredoc, an inherited helper script, or a `bash -c` subshell — saw the variable unset and silently fell back to its default (typically an empty string or "."), writing artifacts to the workflow cwd instead of the nominal artifacts directory. Always build subprocessEnv from process.env plus the three well-known directories, then allow explicit envVars to override. Compile-time substitution behavior is unchanged; existing scripts that do not reference these variables are unaffected; user-supplied envVars still win on conflict. * fix(workflow): substitute $nodeId.output refs in approval messages (#1426) * fix(workflow): substitute \$nodeId.output refs in approval messages Approval node messages were emitted as raw strings, bypassing the substituteNodeOutputRefs() pass that prompt/bash/loop/cancel nodes all run. This made interactive workflows like atlas-onboard show literal "\$gather-context.output.repo_name" placeholders to humans at HITL gates, leaving them unable to know what they were approving. Fix: rendered the approval.message through substituteNodeOutputRefs once at the top of the standard approval gate path, then used the resolved string in all 4 emission sites (safeSendMessage, createWorkflowEvent, pauseWorkflowRun, event-emitter). Test: new dag-executor.test case wires a structured-output upstream node into an approval node and asserts pauseWorkflowRun receives the substituted message ("Repo: hcr-els | App: CCELS | Port: 3012") rather than the literal placeholders. Repro: any workflow with an approval node whose message references \$nodeId.output[.field]. Observed in the wild on atlas-onboard's confirm-context HITL gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(workflow): extend approval-substitution test to cover all 4 emission sites Per CodeRabbit review: the original test only verified pauseWorkflowRun received the substituted message, but the fix touches 4 emission sites. A future regression at safeSendMessage / createWorkflowEvent / event-emitter would silently leave the test passing while users still saw raw $node.output placeholders. Adds two additional assertions: - platform.sendMessage prompt contains substituted message + does NOT contain literal $gather-context.output placeholders - The persisted approval_requested workflow event's data.message is substituted Event-emitter assertion deferred (no existing pattern for spying on the global emitter in this test file). Two of three secondary surfaces covered closes the practical regression risk — both are user-visible (chat prompt + audit-log event); the emitter is internal only. Test count: 7 pass / 22 expect() (was 18). Full suite 193 pass / 353 expect() — no regressions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(workflows): expose $LOOP_PREV_OUTPUT in loop node prompts (#1286) (#1367) * feat(workflows): expose $LOOP_PREV_OUTPUT in loop node prompts (#1286) Adds a new substitution variable that carries the previous loop iteration's cleaned output into the next iteration's prompt. Empty on iteration 1; the prior iteration's output (after stripCompletionTags) on iteration 2+. Why: fresh_context: true loops have no way to reference what the previous pass produced or why it failed without dragging the full session forward. $LOOP_PREV_OUTPUT closes that gap with zero session-cost — same trust boundary as $nodeId.output, no new external surface. Changes: - packages/workflows/src/executor-shared.ts: substituteWorkflowVariables accepts a 10th positional loopPrevOutput arg and substitutes $LOOP_PREV_OUTPUT (defaults to ''). - packages/workflows/src/dag-executor.ts: executeLoopNode passes lastIterationOutput on iteration 2+ (and explicit '' on iteration 1 / the first iteration of an interactive resume, since lastIterationOutput is a per-call variable that does not survive resume metadata). - Unit tests: 3 new cases in executor-shared.test.ts. - Integration tests: 2 new cases in dag-executor.test.ts verifying the prompt sent to the AI on iter 1 vs iter 2, and that the value reflects cleaned output (no <promise> tags). - Docs: variables.md, loop-nodes.md (new "Retry-on-failure" pattern), CLAUDE.md variable reference. Backward compatibility: prompts that don't reference $LOOP_PREV_OUTPUT are unaffected. All 843 workflow tests + type-check + lint + format:check + bun run validate pass locally. * docs: address coderabbit review on variables/loop-nodes - variables.md: include $LOOP_PREV_OUTPUT in substitution-order list and availability table to match the new variable row at line 30 - loop-nodes.md: document the interactive-resume exception where the first iteration after an approval-gate resume still receives an empty $LOOP_PREV_OUTPUT regardless of iteration number (per dag-executor.ts L1781-1783 where i === startIteration always clears prev output) * docs(changelog): add Unreleased entry for $LOOP_PREV_OUTPUT (#1367 review) * test(loop): add resume-from-approval integration test for $LOOP_PREV_OUTPUT (#1367 review) Per maintainer-review-pr suggestion (Wirasm): two-call integration test covering the resume-from-approval scenario. - Call 1: fresh interactive loop pauses at the gate after iteration 1 and asserts $LOOP_PREV_OUTPUT substitutes to empty on iter 1 (no prior output) plus the gate pause is recorded. - Call 2: resumed run with metadata.approval populated. The first resumed iteration must substitute $LOOP_PREV_OUTPUT to '', NOT to the paused run's iter-1 output (which lived in a different process and is not persisted). $LOOP_USER_INPUT still flows through as normal. Locks the documented invariant at dag-executor.ts:1769-1772. --------- Co-authored-by: voidborne-d <DottyEstradalco@allergist.com> * feat(maintainer-standup): surface contributor replies since last run (#1457) The brief was missing a key signal — when contributors reply on PRs or issues, the maintainer wouldn't see it explicitly. Empirically reviewed PR replies were buried under aggregate updatedAt timestamps with no indication of WHO replied or WHAT they said. This adds a new "Replies waiting on you" section to the daily brief, sourced from two paginated GitHub API calls scoped by since=last_run_at: - /repos/{o}/{r}/issues/comments PR + issue conversation comments - /repos/{o}/{r}/pulls/comments inline code-review comments Filters applied: - Skip the maintainer's own comments (gh_handle from profile.md) - Skip GitHub bot accounts (login ending in [bot]) — coderabbitai, chatgpt-codex-connector, dependabot, etc. They post a constant churn of automated review tooling that drowns out human replies; the maintainer wants the latter. Output is grouped by PR/issue number with kind classification: - issue comment on a non-PR issue - pr_conversation PR conversation-level comment - pr_review inline code-review comment (most actionable — usually needs a code-level response, so kind upgrades to pr_review whenever review comments arrive on a PR that also has conversation ones) Sorted by recency (newest reply first). Synthesizer reads gh-data.output.replies_since_last_run and renders a section. Verified on a backdated state.json (last_run_at = yesterday morning): 22 human replies on 22 PRs/issues, bot noise f…
…ARCHON_DATA semantics (coleam00#1518) * docs(docker): clarify ARCHON_HOME/ARCHON_DATA Docker semantics and add ~/.claude/ persistence path Closes coleam00#1517 Operators setting ARCHON_HOME or ARCHON_DATA in .env see them leak into the container env via env_file but neither is read where they expect: ARCHON_HOME is silently overridden to /.archon by paths.archon-paths.ts:56-59, and ARCHON_DATA is a host-side compose substitution token that no TS source reads. Separately, the image runs as appuser with /home/appuser, but no compose file mounts /home/appuser/.claude/, so user-installed Claude Code skills/prompts are wiped on every container rebuild. Five small, independent changes — all docs/config plus a tiny entrypoint hardening: - .env.example: annotate ARCHON_HOME (ignored in Docker) and ARCHON_DATA (compose-only, not read by source) inline so the leak is no longer surprising. - docker-compose.override.example.yml: append a commented claude_home named-volume mount on /home/appuser/.claude with the matching top-level volumes key. Stays opt-in to avoid forcing an extra volume on operators who do not use Claude Code. - docs/deployment/docker.md: add a :::note admonition under "Data Directory" explaining the env-var leak, plus a new "Persisting Claude Code config" subsection with the override-file recipe and a host-bind-mount alternative. - docs/reference/configuration.md: add the Docker caveats to the ARCHON_HOME and ARCHON_DATA table rows. - docker-entrypoint.sh: chown /home/appuser/.claude when present (mirrors the existing /.archon chown, no-op when no mount), and emit one-line stderr warnings when ARCHON_HOME or ARCHON_DATA is set in the container env so the leak is visible instead of silent. No TypeScript source changed; no behavior change for operators who do not set these vars. * feat(docker): persist /home/appuser by default for Claude/Codex/Pi config Mount /home/appuser as a named volume (archon_user_home) by default so user-specific state survives container rebuilds without any opt-in: - Claude Code: skills, commands, agents, hooks, MCP config, projects (conversation history), memory, OAuth state, keybindings, file-history - Codex: interactive `codex login` auth.json (the env-var path via setup-auth still overwrites this on every container start) - Pi: ~/.pi/agent/auth.json from interactive `pi /login` (Archon's Pi adapter reads this on every request per CLAUDE.md) - ~/.gitconfig (author identity, signing config, custom aliases plus the safe.directory entries baked into the image) - ~/.bash_history, ~/.config/gh/ from interactive logins Operators can swap the named volume for a host bind-mount path by setting ARCHON_USER_HOME in .env, mirroring the ARCHON_DATA pattern. Path must be writable by UID 1001; the entrypoint chowns /home/appuser on every start so bind-mount UID drift is handled automatically. Replaces the opt-in claude_home recipe in docker-compose.override.example.yml with a comment pointing to the new default + ARCHON_USER_HOME env var. * fix(docker): address review issues from /home/appuser persistence Three concerns surfaced by review of the persistence change: 1. safe.directory accumulation in ~/.gitconfig With /home/appuser persisted, the entrypoint's `git config --add safe.directory` would append duplicate entries on every restart. Now checks via `--get-all | grep -qxF` before adding. 2. Silent stale-credential reuse in setup-auth When CODEX_* env vars are absent, setup-auth previously early-returned silently — fine when /home/appuser was ephemeral, but now a persisted ~/.codex/auth.json from an earlier run with creds is silently reused. Now warns at startup if the file exists without env vars set, pointing the operator at how to reset. 3. Bind-mount path missing image-baked ~/.gitconfig Docker only copies image content into named volumes on first creation, not into bind mounts. The four safe.directory entries baked at Dockerfile:175-179 don't reach an ARCHON_USER_HOME bind-mount path. Functionally OK — the entrypoint runtime loop covers /.archon repos — but documented as a caveat in the docker.md bind-mount section. * feat(claude): default settingSources to project + user The /home/appuser persistence added in this PR makes ~/.claude/ survive container rebuilds, but Claude SDK was started with settingSources: ['project'], so user-installed skills/commands/agents at ~/.claude/ were never loaded — defeating the persistence promise from coleam00#1517. Default now includes 'user'. Operators who want strict project-only scoping can set assistants.claude.settingSources: [project] explicitly in .archon/config.yaml. Updates the test that locked in the old default and three docs that referenced it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(claude): clean up stale settingSources references and add CHANGELOG Address findings from the comprehensive PR review on the d7757a8 settingSources default flip: - types.ts JSDoc @default tag updated from ['project'] to ['project', 'user'] and broadened to describe skills/commands/agents/hooks scope, not just CLAUDE.md. - CLAUDE.md inline yaml comments around settingSources reflect the new default — both project and user are loaded by default; omitting both restricts to project-only. - reference/configuration.md `### Claude settingSources` subsection rewritten end-to-end. The previous version still framed `user` as an opt-in addition to a project-only default; now correctly describes the default as both, with the opt-out recipe. - CHANGELOG.md [Unreleased] section now documents both Docker /home/appuser persistence and the settingSources default flip — the flip is a non-Docker behavioral change that affects all environments and warrants a release note. - Regression test added for the explicit settingSources: ['project'] opt-out path. Locks in the contract that a future refactor dropping the `?? ['project', 'user']` guard cannot silently widen scope. - setup-auth.ts outer comment refreshed — the early-return path now has two branches (warn-on-stale-creds vs skip-when-codex-unavailable). --------- Co-authored-by: Thomas <info@smartcode.diy> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…coleam00#1444) * chore: update Homebrew formula for v0.3.9 * chore(release-skill): use --help (not version) for Step 1.5 smoke probe (#1359) The pre-flight binary smoke does a bare `bun build --compile` — it deliberately skips `scripts/build-binaries.sh` to stay fast. That means packages/paths/src/bundled-build.ts retains its dev defaults, including BUNDLED_IS_BINARY = false. version.ts branches on BUNDLED_IS_BINARY: when true it returns the embedded string; when false it calls getDevVersion(), which reads package.json at `SCRIPT_DIR/../../../../package.json`. Inside a compiled binary SCRIPT_DIR resolves under `$bunfs/root/`, the walk produces a CWD- relative path that doesn't exist, and the smoke aborts with "Failed to read version: package.json not found" — a false positive. Hit during the 0.3.8 release attempt: the real Pi lazy-load fix was working end-to-end; the smoke test was the only thing failing. Use --help instead. It exercises the same module-init graph (so it still catches the real failure modes the skill lists — Pi package.json init crash, Bun --bytecode bugs, CJS wrapper issues, circular imports under minify) but has no dev/binary branch, so no false positive. Also add a longer comment block explaining why --help is preferred, so this doesn't get "normalized" back to `version` by a future drive-by. * chore(test-release-skill): preserve archon-stable across test cycles The brew path of /test-release runs `brew uninstall` in Phase 5 to leave the system in its pre-test state. For operators using the dual-homebrew pattern (renamed brew binary at `/opt/homebrew/bin/archon-stable` so it coexists with a `bun link` dev `archon`), that uninstall wipes the Cellar dir the `archon-stable` symlink points into → `archon-stable` becomes dangling → `brew cleanup` sweeps it away on the next brew op. Next time the operator wants stable, they have to manually re-run `brew-upgrade-archon`. Fix: make the skill aware of `archon-stable` and restore it transparently. - Phase 2 item 4: detect the `archon-stable` symlink before any brew op; export `ARCHON_STABLE_WAS_INSTALLED=yes` so Phase 5 knows to restore it. Only triggers for the brew path (curl-mac/curl-vps don't touch brew so they leave `archon-stable` alone). - Phase 5 brew path: after `brew uninstall + untap`, if the flag was set, re-tap + re-install + rename. Verifies the restored `archon-stable` reports a version and warns (non-fatal) if the rename target is missing. Documents the tradeoff: the restored version is "whatever the tap ships today", not necessarily the pre-test version — usually that's what the operator wants (the release they just tested becomes stable) but the back-version-QA case requires a manual `brew-upgrade-archon` after. - Phase 1 confirmation banner now mentions that `archon-stable` will be preserved so the operator isn't surprised by the reinstall during Phase 5. No changes to curl-mac/curl-vps paths. No changes to Phase 4 test suite. * fix(providers/pi): install PI_PACKAGE_DIR shim so Pi workflows run in a compiled binary (#1360) v0.3.9 made Pi boot-safe: lazy-loading its imports meant `archon version` no longer crashed on `@mariozechner/pi-coding-agent/dist/config.js`'s module-init `readFileSync(getPackageJsonPath())`. That's what the `provider-lazy-load.test.ts` regression test guards. The fix was only half the problem though. When a Pi workflow actually runs, sendQuery() triggers the dynamic import — and Pi's config.js module-init fires then, hitting the exact same ENOENT on `dirname(process.execPath)/package.json`. Discovered by running `archon workflow run test-pi` against a locally-compiled 0.3.9 binary: [main] Failed: ENOENT: no such file or directory, open '/private/tmp/package.json' at readFileSync (unknown) at <anonymous> (/$bunfs/root/archon-providertest:184:7889) at init_config Boot-safe ≠ runtime-safe. The `/test-release` run for 0.3.9 passed because it only exercised `archon-assist` (Claude); Pi was never actually invoked on the released binary. Fix: before the dynamic `import('@mariozechner/pi-coding-agent')` in sendQuery, install a PI_PACKAGE_DIR shim. Pi's config.js checks `process.env.PI_PACKAGE_DIR` first in its `getPackageDir()` and short-circuits the `dirname(process.execPath)` walk. We write a minimal `{name, version, piConfig:{}}` stub to `tmpdir()/archon-pi-shim/package.json` (idempotent — existsSync check) and set the env var. Pi only reads `piConfig.name`, `piConfig.configDir`, and `version` from that file, all optional, so the stub surface is genuinely minimal. Localized to PiProvider: no global state, no mutation of any shared config, no upstream fork. Claude and Codex providers are unaffected (their SDKs don't have this class of module-init side effect). Verified end-to-end: built a compiled archon binary with this patch, ran `archon workflow run test-pi --no-worktree` (Pi workflow with model `anthropic/claude-haiku-4-5`), got a clean response. Before the patch, same binary crashed at `dag_node_started` with the ENOENT above. Regression test added: asserts `PI_PACKAGE_DIR` is set after sendQuery hits even its fast-fail "no model" path. Together with the existing `provider-lazy-load.test.ts` (boot-safe) this covers both halves. * feat(providers): autodetect canonical binary install paths for Claude and Codex (#1361) Both binary resolvers previously stopped at env-var + explicit config and threw a "not found" error when neither was set. Users who followed the upstream-recommended install flow (Anthropic's `curl install.sh` for Claude, `npm install -g @openai/codex`) still had to manually set either `CLAUDE_BIN_PATH` / `CODEX_BIN_PATH` or the corresponding config field before any workflow could run. Add a tier-N autodetect step between the explicit config tier and the install-instructions throw. Purely additive: env and config still win when set (precedence covered by new tests). On autodetect miss, the same install-instructions error fires as before. Claude probe list (verified against docs.claude.com "Uninstall Claude Code → Native installation" section): - $HOME/.local/bin/claude (mac/linux native installer) - $USERPROFILE\.local\bin\claude.exe (Windows native installer) Codex probe list (verified against openai/codex README; npm global- install puts the binary at `{npm_prefix}/bin/<name>` on POSIX, `{npm_prefix}\<name>.cmd` on Windows): - $HOME/.npm-global/bin/codex (user-set `npm config set prefix`) - /opt/homebrew/bin/codex (mac arm64 with homebrew-node) - /usr/local/bin/codex (mac intel / linux system node) - %APPDATA%\npm\codex.cmd (Windows npm global default) - $HOME\.npm-global\codex.cmd (Windows user-set prefix) Not probed (explicit override still required): - Custom npm prefixes — `npm root -g` would need a subprocess per resolve, too much surface for a probe helper - `brew install --cask codex` — cask layout isn't a PATH binary - Manual GitHub Releases extracts — placement is user-determined - `~/.bun/bin/codex` — not documented in openai/codex README Pi provider intentionally has no equivalent change: the Pi SDK is bundled into the archon binary (no subprocess), so there's no "binary" to resolve. Pi auth lives at `~/.pi/agent/auth.json` which the SDK already finds by default, and the PR A shim (`PI_PACKAGE_DIR`) handles the package-dir case via Pi's own documented escape hatch. E2E verified: removed both config entries from ~/.archon/config.yaml, rebuilt compiled binary, ran `archon workflow run archon-assist` and a Codex workflow. Logs showed `source: 'autodetect'` for both, responses returned cleanly. * fix(providers/test): use os.homedir() instead of $HOME in claude binary autodetect test The native-installer autodetect test computed its expected path from process.env.HOME, but the implementation uses node:os homedir(). On Windows, HOME is typically unset (Windows uses USERPROFILE), so the test fell back to '/Users/test' while the resolver returned the real home dir — making the spy's path-equality check fail and breaking CI on windows-latest. Mirror the implementation by importing homedir() from node:os and joining with node:path so the expected path matches the actual platform-resolved home and separator. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(server): contain Discord login failure so it doesn't kill the server (#1365) Reported in #1365: a user running `archon serve` with DISCORD_BOT_TOKEN set but the "Message Content Intent" toggle disabled in the Discord Developer Portal saw the entire server crash with `Used disallowed intents`. Discord rejects the gateway connection (close code 4014) when a privileged intent is requested without being enabled, and the unguarded `await discord.start()` propagated the error all the way up, taking the web UI down with it. Wrap discord.start() in try/catch — log the failure with an actionable hint (special-cased for the disallowed-intent error) and continue running. Other adapters and the web UI come up regardless. The shutdown handler already uses optional chaining (`discord?.stop()`) so nulling discord after a failed start is safe. Other adapters (Telegram, Slack, GitHub, Gitea, GitLab) have the same unguarded-start pattern but are out of scope for this fix — addressing them is tracked separately. Also expanded the Discord setup docs with a caution callout that names the exact error string and the new log event so users can grep for both. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(script-nodes): dedicated guide + teach the archon skill (#1362) * docs(script-nodes): add dedicated guide and teach the archon skill how to write them Script nodes (script:) have been a first-class DAG node type since v0.3.3 but were documented only as one-liners in CLAUDE.md and a CI smoke test. Claude Code reading the archon skill would see "Four Node Types: command, prompt, bash, loop" and reach for bash+node/python one-liners instead of a proper script node — losing bun's --no-env-file isolation, uv's --with dependency pins, and the .archon/scripts/ reuse story. - New packages/docs-web/src/content/docs/guides/script-nodes.md mirroring the structure of loop-nodes.md / approval-nodes.md: schema, inline vs named dispatch, runtime/deps semantics, scripts directory precedence (repo > home), extension-runtime mapping, env isolation, stdout/stderr contract, patterns, and the explicit list of ignored AI fields. - guides/authoring-workflows.md and guides/index.md updated so the new guide is discoverable from both the node-types table and the guides landing page. - reference/variables.md calls out the no-shell-quote difference between bash: and script: substitution — a subtle correctness trap when adapting a bash pattern into a script node. - Sidebar order bumped +1 on hooks/mcp-servers/skills/global-workflows/ remotion-workflow to slot script-nodes at order 5 next to the other node-type guides. - .claude/skills/archon/SKILL.md: replaces stale "Four Node Types" (which also silently omitted approval and cancel) with the accurate seven, with a script-node code block showing both inline and named patterns. - references/workflow-dag.md: full Script Node section covering dispatch, resolution, deps, stdout contract, and the list of AI-only fields that are ignored; validation-rules list updated. - references/dag-advanced.md and references/variables.md: retry-support line corrected; no-shell-quote note added. - examples/dag-workflow.yaml: added an extract-labels TypeScript script node and updated the header comment. * fix(docs): review follow-ups for script-node guide - skills example: extract-labels was reading process.env.ISSUE_JSON which is never set; use String.raw`$fetch-issue.output` so the upstream bash node's JSON is actually consumed - guides/script-nodes.md + skills/workflow-dag.md: idle_timeout is accepted but ignored on script (and bash) nodes — executeScriptNode only reads node.timeout. Clarify that script/bash use `timeout`, not idle_timeout - archon-workflow-builder.yaml: prompt enumerated only bash/prompt/command/loop, so the AI builder could never propose script or approval nodes. Add both (plus examples + rule about script output not being shell-quoted) and regenerate bundled defaults - book/dag-workflows.md + book/quick-reference.md + adapters/web.md: fill in the node-type references that were missing script, approval, and cancel. adapters/web.md also overclaimed "loop" in the palette — NodePalette.tsx only drags command/prompt/bash, so note that the other kinds are YAML-only * docs/skill: general hardening — fix inaccuracies, fill workflow/CLI/env gaps, add good-practices + troubleshooting (#1363) * fix(skill/when): document the full `when:` operator set and compound expressions The skill reference previously stated "operators: ==, != only" which is materially wrong — the condition evaluator supports ==, !=, <, >, <=, >= plus && / || compound expressions with && binding tighter than ||, plus dot-notation JSON field access. An agent authoring a workflow from the skill would think half the operators don't exist. Replaces the single-sentence section with a structured reference covering: - All six comparison operators (string and numeric modes) - Compound expressions with precedence rules and short-circuit eval - JSON dot notation semantics and failure modes - The fail-closed rules in full (invalid expression, non-numeric side, missing field, skipped upstream) Grounded in packages/workflows/src/condition-evaluator.ts. * feat(skill): document Approval and Cancel node types Approval and cancel nodes are first-class DAG node types (approval since the workflow lifecycle work in #871, cancel as a guarded-exit primitive) but the skill never described either one. An agent reading the skill and asked to "add a review gate before implementation" or "stop the workflow if the input is unsafe" would fall back to bash + exit 1, losing the proper semantics (cancelled vs. failed, on_reject AI rework, web UI auto-resume). Approval node coverage (references/workflow-dag.md, SKILL.md): - Full configuration block with message, capture_response, on_reject - The interactive: true workflow-level requirement for web UI delivery - Approve/reject commands across all platforms (CLI, slash, natural language) and the capture_response → $node-id.output flow - Ignored-fields list + the on_reject.prompt AI sub-node exception Cancel node coverage (references/workflow-dag.md, SKILL.md): - Single-field schema (cancel: "<reason>") - Lifecycle: cancelled (not failed); in-flight parallel nodes stopped; no DAG auto-resume path - The "cancel: vs bash-exit-1" decision rule (expected precondition miss vs. check itself failing) - Two canonical patterns — upstream-classification gate, pre-expensive-step gate Validation-rules list updated to enumerate approval/cancel constraints (message non-empty, on_reject.max_attempts range 1-10, cancel reason non-empty), plus a forward note that script: joins the mutually-exclusive set once PR #1362 lands. Placement in both files is after the Loop section and before the validation section, so this commit stays additive with respect to PR #1362's Script node insertion between Bash and Loop — rebase is clean. * feat(skill): document workflow-level fields beyond name/provider/model The skill's Schema section previously showed only name, description, provider, and model at the workflow level — which is most of a stub. Agents asked to "use the 1M-context Claude beta" or "run this under a network sandbox" or "add a fallback model in case Opus rate-limits" had no way to discover that any of these fields existed at the workflow level. Adds a comprehensive Workflow-Level Fields section covering: - Core: name, description, provider, model, interactive (with explicit callout that interactive: true is REQUIRED for approval/loop gates on web UI — a common footgun) - Isolation: worktree.enabled for pin-on/pin-off (the only worktree field at workflow level; baseBranch/copyFiles/path/initSubmodules are config.yaml only, so a cross-reference points there) - Claude SDK advanced: effort, thinking, fallbackModel, betas, sandbox, with explicit per-node-only exceptions (maxBudgetUsd, systemPrompt) - Codex-specific: modelReasoningEffort (with note that it's NOT the same as Claude's effort — this has confused users), webSearchMode, additionalDirectories - A complete worked example combining sandbox + approval + interactive All fields cross-referenced against packages/workflows/src/schemas/workflow.ts and packages/workflows/src/schemas/dag-node.ts. * feat(skill/loop): document interactive loops and gate_message Interactive loop nodes pause between iterations for human feedback via /workflow approve — used by archon-piv-loop and archon-interactive-prd. The skill's Loop Nodes section previously omitted both interactive: true and gate_message entirely, so an agent writing a guided-refinement workflow wouldn't know the feature exists or that gate_message is required at parse time. Adds: - interactive and gate_message rows to the config table (marking gate_message as required when interactive: true — enforced by the loader's superRefine) - A dedicated "Interactive Loops" subsection explaining the 6-step iterate-pause-approve-resume flow - Explicit call-out that $LOOP_USER_INPUT populates ONLY on the first iteration of a resumed session — easy to miss and a common surprise - Workflow-level interactive: true requirement for web UI delivery (loader warning otherwise) so the full-flow example is complete - Note that until_bash substitution DOES shell-quote $nodeId.output (unlike script bodies) — called out since the audit surfaced this inconsistency * fix(skill/cli): complete the CLI command reference with missing lifecycle commands The CLI reference previously documented only list, run, cleanup, validate, complete, version, setup, and chat — missing nearly every workflow lifecycle command an agent needs to operate a paused, failed, or stuck run. The interactive-workflows reference assumed these commands existed without actually documenting them. Adds full documentation for: - archon workflow status — show running workflow(s) - archon workflow approve <run-id> [comment] — resume approval gate (also populates $LOOP_USER_INPUT on interactive loops and the gate node's output when capture_response: true) - archon workflow reject <run-id> [reason] — reject gate; cancels or triggers on_reject rework depending on node config - archon workflow cancel <run-id> — terminate running/paused with in-flight subprocess kill - archon workflow abandon <run-id> — mark stuck row cancelled without subprocess kill (for orphan-cleanup after server crashes — matches the #1216 precedent) - archon workflow resume <run-id> [message] — force-resume specific run (auto-resume is default; this is for explicit override) - archon workflow cleanup [days] — disk hygiene for old terminal runs (with explicit callout that it does NOT transition 'running' rows, a common confusion) - archon workflow event emit — used inside loop prompts for state signalling; documented so agents don't invent their own mechanism - archon continue <branch> [flags] [msg] — iterative-session entry point with --workflow and --no-context flags Also: - Adds --allow-env-keys flag to the `workflow run` flag table with audit-log context and the env-leak-gate remediation use case - Adds an "Auto-resume without --resume" note disambiguating when --resume is needed vs. when auto-resume handles it - Adds --include-closed flag to `isolation cleanup`, which was previously missing; converts the flag list to a structured table - Explains the cancel/abandon distinction (live subprocess vs. orphan) All grounded in packages/cli/src/commands/workflow.ts, continue.ts, and isolation.ts. * feat(skill/repo-init): add scripts/ and state/, three-path env model, per-project env injection The repo-init reference was missing two first-class .archon/ directories (scripts/ since v0.3.3, state/ since the workflow-state feature) and had nothing to say about env — the #1 thing a user hits on first-run when their repo has a .env file with API keys. Directory tree updates: - Adds .archon/scripts/ with the extension->runtime rule (.ts/.js -> bun, .py -> uv) so agents know where to put named scripts referenced by script: nodes. - Adds .archon/state/ with explicit "always gitignore" callout — these are runtime artifacts, not source. Previously undocumented in the skill. - Adds .archon/.env (repo-scoped Archon env) and distinguishes it from the target repo's top-level .env. - Adds a "What each directory is for" list so the structure isn't just a tree with no narrative. .gitignore guidance: - state/ and .env added as must-gitignore (state/ matches CLAUDE.md and reference/archon-directories.md — skill was lagging). - mcp/ demoted to conditional — gitignore only if you hardcode secrets. New "Three-Path Env Model" section: - ~/.archon/.env (trusted, user), <cwd>/.archon/.env (trusted, repo), <cwd>/.env (UNTRUSTED, target project — stripped from subprocess env). - Precedence (override: true across archon-owned paths) and the observable [archon] loaded N keys / stripped K keys log lines so operators can verify what actually happened. - Decision tree for where to put API keys vs. target-project env vs. things Archon shouldn't touch. - Links to archon setup --scope home|project with --force for writing to the right file with timestamped backups. New "Per-Project Env Injection" section: - Documents both managed surfaces: .archon/config.yaml env: block (git-committed, $REF expansion) and Web UI Settings → Projects → Env Vars (DB-stored, never returned over API). - Names every execution surface that receives the injected vars: Claude/Codex/Pi subprocess, bash: nodes, script: nodes, and direct codebase-scoped chat. - Documents the env-leak gate with all 5 remediation paths so an agent hitting "Cannot register: env has sensitive keys" knows the options. Grounded in CHANGELOG v0.3.7 (three-path env + setup flags), v0.3.0 (env-leak gate), and reference/security.md on the docs site. * fix(skill/authoring-commands): correct override paths and add home-scoped commands The file-location and discovery sections described an override layout that does not match the actual resolver. It showed: .archon/commands/defaults/archon-assist.md # Overrides the bundled and claimed `.archon/commands/defaults/` was where repo-level overrides lived. In fact the resolver (executor-shared.ts:152-200 + command- validation.ts) walks `.archon/commands/` 1 level deep and uses basename matching — putting `archon-assist.md` at the top of `.archon/commands/` is the canonical way to override the bundled version. The `defaults/` subfolder is a Archon-internal convention for shipping bundled defaults, not a user-facing override pattern. Also, home-scoped commands (`~/.archon/commands/`, shipped in v0.3.7) were completely absent — agents authoring personal helpers wouldn't know they could live at the user level and be shared across every repo. Changes: - File Location section now shows all three discovery scopes (repo, home, bundled) with precedence ordering and 1-level subfolder rules - Duplicate-basename rule documented as a user error surface - Discovery and Priority section rewritten with accurate 3-step lookup order — no more references to the nonexistent defaults/ override path - Adds the Web UI "Global (~/.archon/commands/)" palette label note so users authoring helpers for the builder know what to expect No code changes — this is a pure fix of stale/incorrect skill reference material. * feat(skill): add workflow good-practices and troubleshooting reference pages Closes two gaps from the audit. The skill previously had zero guidance on designing multi-node workflows (what to avoid, what to reach for first, how to structure artifact chains) and zero guidance on where to look when things go wrong (log paths, env-leak gate remediations, orphan-row cleanup, resume semantics). New references/good-practices.md (9 Good Practices + 7 Anti-Patterns): - Use deterministic nodes (bash:/script:) for deterministic work, AI for reasoning — the single biggest quality lever - output_format required whenever downstream when: reads a field — the most common source of "workflow silently routes wrong" - trigger_rule: none_failed_min_one_success after conditional branches — the classic bug where all_success fails because a skipped when:-gated branch doesn't count as a success - context: fresh requires artifacts for state passing — commands must explicitly "read $ARTIFACTS_DIR/..." when downstream of fresh - Cheap models (haiku) for glue, strong for substance - Workflow descriptions as routing affordances - Validate (archon validate workflows) + smoke-run before shipping - Artifact-chain-first design - worktree.enabled: true for code-changing workflows (reversibility) - Anti-patterns with before/after YAML examples for each (AI-for-tests, free-form when: matching, context: fresh without artifacts, long flat AI-node layers, secrets in YAML, retry on loop nodes, tiny max_iterations, missing workflow-level interactive:, tool-restricted MCP nodes) New references/troubleshooting.md: - Log location (~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl) with jq recipes for common queries (last assistant message, failed events, full stream) - Artifact location for cross-node handoff debugging - 9 Common Failure Modes, each with root cause + concrete fix: - $BASE_BRANCH unresolvable - Env-leak gate (5 remediations) - Claude/Codex binary not found (compiled-binary-only) - "running" forever (AI working / orphan / idle_timeout) - Mid-workflow failure and auto-resume semantics - Approval gate missing on web UI (workflow-level interactive:) - MCP plugin connection noise (filtered by design) - Empty $nodeId.output / field access (4 causes) - Diagnostic command cheat sheet (list, status, isolation list, validate, tail-log, --verbose, LOG_LEVEL=debug) - Escalation protocol (version + validate + log tail + CHANGELOG + issue) SKILL.md routing table now dispatches "Workflow good practices / anti-patterns" and "Troubleshoot a failing / stuck workflow" to the new references so an agent can find them without having to know they exist. * docs(book): update node-types coverage from four to all seven The book is the curated first-contact reading path (landing page → "Get Started" → /book/). Both dag-workflows.md and quick-reference.md were stuck on "four node types" — missing script, approval, and cancel. A user reading the book as their first introduction would form an incomplete mental model, then find three more node types in the reference section later with no explanation of when they arrived. book/dag-workflows.md: - "four node types" → "seven node types. Exactly one mode field is required per node" - Table now lists Command, Prompt, Bash, Script, Loop, Approval, Cancel with one-line "when to use" for each, and cross-links to the dedicated guide pages for Script / Loop / Approval - New sections below the table for Script (inline + named examples with runtime and deps), Approval (with the interactive: true workflow-level note that's easy to miss), and Cancel (guarded-exit pattern) — keeping the existing narrative shape for Bash and Loop book/quick-reference.md: - Node Options table now includes script, approval, cancel rows - agents row added (inline sub-agents, Claude-only) - New "Script-specific fields" and "Approval-specific fields" subsections so the cheat-sheet is actually complete rather than pointing users elsewhere for the required constraints - Retry row callout that loop nodes hard-error on retry — previously omitted - bash timeout note widened to cover script timeout (same semantics) Both files are docs-web content; the CI build on the docs-script-nodes PR (#1362) previously validated the Starlight build path with a similar table addition, so this should render clean. * fix(skill/cli): remove nonexistent \`archon workflow cancel\`, fix workflow status jq recipe Two accuracy issues from the PR code-reviewer (comment 4311243858). C1: \`archon workflow cancel <run-id>\` does NOT exist as a CLI subcommand. The switch at packages/cli/src/cli.ts:318-485 dispatches on list / run / status / resume / abandon / approve / reject / cleanup / event — running \`archon workflow cancel\` hits the default case and exits with "Unknown workflow subcommand: cancel" (cli.ts:478-484). Active cancellation is only available via: - /workflow cancel <run-id> chat slash command (all platforms) - Cancel button on the Web UI dashboard - POST /api/workflows/runs/{runId}/cancel REST endpoint cli-commands.md: removed the \`### archon workflow cancel <run-id>\` subsection; kept the \`abandon\` subsection but made it explicit that abandon does NOT kill a subprocess. Added a call-out box at the bottom of the abandon section explaining where to go for actual cancellation. troubleshooting.md "running forever" section: split the original cancel-vs-abandon advice into three bullets — Web UI / CLI abandon (for orphans, no subprocess kill) / chat \`/workflow cancel\` (for live runs that need interruption). Added an explicit "there is no archon workflow cancel CLI subcommand" parenthetical since the wrong command was being suggested in flow. I1: the \`archon workflow list --json\` diagnostic used an incorrect jq filter. workflow list's --json output (workflow.ts:185-219) has shape { workflows: [{ name, description, provider?, model?, ... }], errors: [...] } with no \`runs\` field — \`jq '.workflows[] | select(.runs)'\` returns empty unconditionally. Replaced with \`archon workflow status --json | jq '.runs[]'\`, which matches the actual shape of workflowStatusCommand at workflow.ts:852+ ({ runs: WorkflowRun[] }). Also tightened the narration to distinguish JSON from human-readable status output. No change to the commit history in this PR — these are follow-up fixes to claims I introduced in earlier commits of this branch (f10b989e for C1, 66d2b86e for I1). * fix(skill): remove env-leak gate references (feature was removed in provider extraction) C2 from the PR code-reviewer (comment 4311243858). The pre-spawn env-leak gate was removed from the codebase during the provider-extraction refactor — see TODO(#1135) at packages/providers/src/claude/provider.ts:908. Zero hits for --allow-env-keys / allowEnvKeys / allow_env_keys / allow_target_repo_keys across packages/. The CLI's parseArgs (cli.ts:182-208) has no --allow-env-keys option, and because parseArgs uses strict: false, an unknown --allow-env-keys would be silently ignored rather than error. What remains accurate and is NOT touched: - Three-Path Env Model section (user/repo archon-owned envs are loaded; target repo <cwd>/.env keys are stripped from process.env at boot) still correctly describes current behavior, grounded in packages/paths/src/strip-cwd-env.ts + env-integration.test.ts - Per-Project Env Injection section (Option 1: .archon/config.yaml env: block; Option 2: Web UI Settings → Projects → Env Vars) is unchanged — both remain the sanctioned way to get env vars into subprocesses Removed claims (all three files): - cli-commands.md: --allow-env-keys flag row in the workflow run flags table - repo-init.md: the "Env-leak gate" subsection at the end of Per-Project Env Injection listing 5 remediations (all of which reference UI/CLI/ config surfaces that don't exist). Replaced with a succinct callout that explains the actual current behavior — target repo .env keys are stripped, workflows that need those values should use managed injection — so the reader still gets the "where to put my env vars" answer - troubleshooting.md: the "Cannot register: codebase has sensitive env keys" section (error message that can no longer be emitted) If the env-leak gate is ever resurrected per TODO(#1135), the docs can be re-added then. The CHANGELOG v0.3.0 entry describing the gate is a historical record of past behavior and does not need to be rewritten. * fix(skill/troubleshooting): correct JSONL event type names and field name C3 from the PR code-reviewer (comment 4311243858). The troubleshooting reference's event-types table used _started / _completed / _failed suffixes, but packages/workflows/src/logger.ts:19-30 shows the actual WorkflowEvent.type enum is: workflow_start | workflow_complete | workflow_error | assistant | tool | validation | node_start | node_complete | node_skipped | node_error The second jq recipe also queried `.event` but the discriminator is `.type`. Fixes: - Event table: renamed columns (_started → _start, _completed → _complete, _failed → _error). Explicitly called out the field name as `type` so the reader knows what jq selector to use - Replaced the "tool_use / tool_result" row with a single `tool` row and listed its actual payload fields (tool_name, tool_input, duration_ms, tokens) — tool_use/tool_result are SDK message kinds that appear within the AI stream, not top-level log event types - Added a `validation` row (was missing; it's emitted by workflow-level validation calls with `check` and `result` fields) - Removed `retry_attempt` row — this event type is not emitted to the JSONL file. Retry bookkeeping goes through pino logs, not the workflow log file - Added an explicit callout that loop_iteration_started / loop_iteration_completed (and other emitter-only events) go through the workflow event emitter + DB workflow_events table, NOT the JSONL file. Pointed readers to the DB or Web UI for loop-level detail. This distinguishes the two parallel event systems — easy to conflate (store.ts:11-17 uses _started/_completed/_failed for the DB side, logger.ts uses _start/_complete/_error for JSONL) - Fixed the "all failed events" jq recipe: .event → .type and _failed → _error - Minor cleanup: the inline "tool_use events" mention in the "running forever" section said the wrong event name — updated to "tool or assistant events in the tail" Grounded in packages/workflows/src/logger.ts (canonical JSONL event shape) and packages/workflows/src/store.ts (the parallel DB event naming, which the reviewer correctly flagged as different and worth keeping distinct). * fix(skill): two stragglers from the code-reviewer audit Cleanup of two references that slipped through the earlier C1 and C3 fixes: - references/troubleshooting.md:126: \`node_failed\` → \`node_error\` (the "Node output is empty" diagnostics section references the JSONL log, which uses the logger.ts enum — not the DB workflow_events table which does use \`node_failed\`). The C3 fix corrected the event table and one jq recipe but missed this inline mention. - references/interactive-workflows.md:106: removed \`archon workflow cancel <run-id>\` (nonexistent CLI subcommand) from the troubleshooting bullet. This was pre-existing before the hardening PR but fell within the C1 remediation scope. Replaced with the correct triage: reject (approval gate only) vs abandon (orphan cleanup, no subprocess kill) vs chat /workflow cancel (actual subprocess termination). Grounded in the same sources as the earlier C1/C3 commits: packages/cli/src/cli.ts:318-485 (no cancel case) and packages/workflows/src/logger.ts:19-30 (JSONL type enum). * feat(skill): point to archon.diy as the canonical docs source The skill had no reference to archon.diy (the live docs site built from packages/docs-web/). Several reference files said "see the docs site" without naming the URL, leaving the agent to guess or grep the repo for the hostname. An agent with the skill loaded should know that when the distilled reference pages don't cover a case, the full canonical docs are one WebFetch away. SKILL.md: new "Richer Context: archon.diy" section between Routing and Running Workflows. Covers: - When to reach for the live docs (longer examples, tutorial framing, features the skill only mentions in passing, "where's that documented?" user questions) - URL map — 13 starting points covering getting-started, book (tutorial series), guides/ (authoring + per-node-type + per-node-feature), reference/ (variables, CLI, security, architecture, configuration, troubleshooting), adapters/, deployment/ - Precedence: skill refs first (context-cheap, tuned for agents), docs site as escalation. Prevents agents defaulting to WebFetch when a local skill ref already covers the answer Also upgrades the 5 existing generic "docs site" mentions across reference files to concrete archon.diy URLs with anchor fragments where helpful: - good-practices.md: Inline sub-agents pattern → archon.diy/guides/ authoring-workflows/#inline-sub-agents - troubleshooting.md: "Install page on the docs site" → archon.diy/ getting-started/installation/ - workflow-dag.md: "Workflow Description Best Practices" → anchor link; sandbox schema reference → archon.diy/guides/authoring-workflows/ #claude-sdk-advanced-options - repo-init.md: Security Model reference → archon.diy/reference/ security/#target-repo-env-isolation (deep-link into the section that covers the <cwd>/.env strip behavior) URL source of truth: astro.config.mjs:5 (site: 'https://archon.diy'). URL structure mirrors packages/docs-web/src/content/docs/<section>/ <page>.md — verified by the 62 pages the docs build produces. * chore(workflows): switch default Opus pin to opus[1m] alias (#1395) Anthropic's Opus 4.7 landed 2026-04-16; on the Anthropic API, opus / opus[1m] now resolve to 4.7 with a 1M context window at standard pricing. Using the alias instead of the hard-pinned claude-opus-4-6[1m] lets bundled default workflows auto-track the recommended Opus version. No explicit effort is set, so nodes inherit the per-model default (xhigh on 4.7, high on 4.6). * fix(workflow): migrate piv-loop plan handoff to $ARTIFACTS_DIR (#1398) * fix(workflow): migrate piv-loop plan handoff to $ARTIFACTS_DIR (#1380) The create-plan node used a relative path (.claude/archon/plans/{slug}.plan.md) that the AI agent would sometimes write to a different location, breaking all downstream nodes that glob for the plan file. Migrated all plan/progress file references to $ARTIFACTS_DIR/plan.md and $ARTIFACTS_DIR/progress.txt, matching the pattern used by archon-fix-github-issue and other workflows. Changes: - Replace slug-based plan path with $ARTIFACTS_DIR/plan.md in create-plan node - Replace ls -t glob discovery with direct $ARTIFACTS_DIR/plan.md reads in refine-plan, code-review, and fix-feedback nodes - Replace empty-string guard with file-existence check in implement-setup bash - Migrate progress.txt references in implement loop to $ARTIFACTS_DIR/ - Add explicit plan/progress paths in finalize node - Regenerated bundled-defaults.generated.ts Fixes #1380 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(workflow): address review findings in archon-piv-loop - Rename 'Step 2: Write the Plan' to 'Step 2: Plan File Location' to eliminate the duplicate heading that collided with Step 3's identical title in the create-plan node - Guard implement-setup against a 0-task plan file: exit 1 with a clear error when no '### Task N:' sections are found, preventing a silent no-op implement loop - Remove 2>/dev/null from code-review commit so pre-commit hook failures and other stderr are visible to the agent instead of silently swallowed - Replace '|| true' on git push in finalize with an explicit WARNING echo so push failures (auth, upstream conflict, no remote) surface to the agent rather than being silently ignored - Regenerate bundled-defaults.generated.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(workflows): regenerate bundled defaults to match opus[1m] alias The bundle was stale relative to the YAML sources after #1395 merged — check:bundled was failing CI. Regenerated; no YAML edits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(workflows): add anyFailed status derivation coverage for DAG executor (#1403) PIV Task 1: Adds three new tests in a dedicated describe block 'executeDagWorkflow -- final status derivation' covering the anyFailed branch (dag-executor.ts ~line 2956) that previously had no direct test: - one success + one independent failure calls failWorkflowRun (not completeWorkflowRun) - multiple successes + one failure calls failWorkflowRun (not completeWorkflowRun) - trigger_rule: none_failed skips dependent node but anyFailed still marks run failed Fixes #1381. * docs/skill: add parameter-matrix.md quick-lookup reference New reference for the archon skill: a single-glance lookup of which parameter works on which node type, an intent-based "how do I..." table, a consolidated silent-failure catalog, and an inline agents: section (previously only referenced via archon.diy). Purpose is complementary, not duplicative: - workflow-dag.md remains the authoring guide - dag-advanced.md remains the hooks/MCP/skills/retry deep-dive - good-practices.md remains the patterns and anti-patterns - parameter-matrix.md is the grep-this-first lookup when you know the outcome you want but not which field gets you there Also registers the new reference in SKILL.md routing table. * docs: point contributors at PR template and Closes #N convention Add explicit references to .github/PULL_REQUEST_TEMPLATE.md in both CONTRIBUTING.md and CLAUDE.md, plus a reminder to link issues with Closes/Fixes/Resolves so they auto-close on merge. Repo-triage runs were flagging dozens of partially-filled or unlinked PRs each cycle. * feat(workflows): add maintainer-standup workflow for daily PR/issue triage (#1428) * feat(workflows): add maintainer-standup workflow for daily PR/issue triage Daily morning briefing that pulls origin/dev, triages all open PRs and assigned issues against direction.md, and surfaces progress vs. the previous run. Designed for live-checkout use (worktree.enabled: false) so it can read its own state. Layout under .archon/maintainer-standup/: - direction.md (committed) — project north-star: what Archon IS / IS NOT. Drives PR P4 polite-decline classification with cited clauses. - README.md / profile.md.example — setup docs and template for new maintainers. - profile.md, state.json, briefs/YYYY-MM-DD.md — gitignored, per-maintainer. Engine: - 3 parallel gather scripts in .archon/scripts/maintainer-standup-*.ts (git-status, gh-data, read-context) — bun runtime, JSON stdout. - Synthesis node: command file with output_format schema for { brief_markdown, next_state }. - Persist node: tiny inline bun script writes both to disk. Run-to-run continuity: state.json carries observed_prs/issues snapshots, so the next run can detect what merged, what closed, what the maintainer shipped, and which carry-over items aged past N days. Also adds .archon/** to the ESLint global ignore list (matches the existing .claude/skills/** pattern) since .archon/ is user content and not part of any tsconfig project. * fix(maintainer-standup): address CodeRabbit review on #1428 - gh-data: bump --limit 100 → 1000 on all_open_prs and warn loudly when the cap is hit; preserves the observed_prs invariant the next-run "resolved since last run" diff depends on. (CodeRabbit critical) - maintainer-standup.md: clarify P1 CI signal — the gathered payload only carries mergeStateStatus, not statusCheckRollup; for borderline P1s, drill in via `gh pr checks <n>`. (CodeRabbit minor) - workflow.yaml persist: write briefs under local YYYY-MM-DD (sv-SE locale) instead of UTC ISO date, so an evening run doesn't file tomorrow's brief and break recent_briefs lookups. (CodeRabbit minor) - workflow.yaml persist: wrap state/brief writes in try/catch; on failure dump brief_markdown and next_state to stderr so a 5-minute Sonnet synthesis isn't lost to a transient disk error. (CodeRabbit minor) - gh-data + git-status: switch from execSync (shell-string) to execFileSync (argv array) for git/gh invocations. Defense-in-depth against shell metacharacters in values that pass through (esp. the gh_handle from profile.md). (CodeRabbit nitpick) * feat(workflows): support explicit tags in workflow YAML (#1190) Add optional `tags: string[]` to `workflowBaseSchema`. Explicit values take precedence over keyword inference; `tags: []` suppresses inference end-to-end; omitting the field falls back to inference (backwards compatible). Non-array values warn-and-ignore matching the sibling `worktree`/`additionalDirectories` patterns. * feat(workflows): add maintainer-review-pr and group maintainer workflows under maintainer/ (#1430) * feat(workflows): add maintainer-review-pr and group maintainer workflows under .archon/workflows/maintainer/ Adds the maintainer-review-pr workflow — a Pi/Minimax-based PR triage flow that gates on direction alignment, scope focus, and PR-template quality before doing any deep review. If the gate clears, runs the five review aspects (code/error-handling/test-coverage/comment-quality/ docs-impact) as parallel Archon nodes and auto-posts a synthesized review comment. If the gate fails (direction conflict, multiple concerns, sprawling scope), drafts a polite-decline comment and pauses for the maintainer's approval before posting. Reorganizes the existing maintainer-standup workflow into the same subfolder so all maintainer-facing workflows live together. Subfolder grouping is supported by the workflow loader (1 level deep, resolution by filename). What lands: - .archon/workflows/maintainer/maintainer-standup.yaml (moved from .archon/workflows/maintainer-standup.yaml) - .archon/workflows/maintainer/maintainer-review-pr.yaml (new) - .archon/commands/maintainer-review-{gate,code-review,error-handling, test-coverage,comment-quality,docs-impact,synthesize,report}.md (new, Pi-tuned variants of the existing review-agent commands so they avoid Claude-only Task / sub-agent patterns) Pi/Minimax integration: - Uses provider: pi, model: minimax/MiniMax-M2.7 — verified via the e2e-minimax-smoke test that Pi correctly routes to Minimax (session jsonl confirms provider=minimax) and that Pi's best-effort output_format parser handles the gate's nested schema. - Two test runs landed real comments: a direction-decline on PR #1335 and a deep-review on PR #1369. Both were posted to GitHub via the workflow's gh pr comment node. * chore(workflows): also group repo-triage under .archon/workflows/maintainer/ repo-triage is the third maintainer-facing workflow alongside maintainer-standup and maintainer-review-pr; group it in the same subfolder for consistency. Subfolder resolution is by filename so the workflow name is unchanged. * feat(pi): use ModelRegistry to support custom models and skip auth for unmapped providers (#1284) Closes #1096. - Switch Pi provider model lookup from pi-ai's getModel() (static catalog only) to ModelRegistry.create(authStorage).find() so user-configured custom models in ~/.pi/agent/models.json (LM Studio, ollama, llamacpp, custom OpenAI-compatible endpoints) are discoverable. - Remove the local lookupPiModel helper. - For env-var-mapped providers (anthropic, openai, etc.) still throw with a pi /login hint when credentials are missing. For unmapped providers, log pi.auth_missing at info and continue so local models that don't need credentials work without ceremony. - Surface modelRegistry.getError() in the not-found message and emit pi.model_not_found so users debugging custom-provider configs see the real cause (e.g. missing baseUrl in models.json). - Guard AuthStorage.create() and ModelRegistry.create() with try/catch so a malformed ~/.pi/agent/auth.json surfaces with Pi-framed context instead of a raw SDK stack trace. - Document the credential-free path for local providers in ai-assistants.md. Co-authored-by: Matt Chapman <Matt@NinjitsuWeb.com> * chore(workflows): group smoke-test workflows under test-workflows/ + add e2e-minimax-smoke (#1431) * chore(workflows): group all smoke-test workflows under .archon/workflows/test-workflows/ Move the 7 existing e2e-*.yaml smoke tests plus the new e2e-minimax-smoke test into a dedicated subfolder. Subfolder grouping is supported by the workflow loader (1 level deep, resolution by filename) so workflow names are unchanged. Mirrors the .archon/workflows/maintainer/ split landing in #1430. Also adds e2e-minimax-smoke.yaml — a sanity check that Pi correctly routes to Minimax M2.7 via the user's local pi auth, and that Pi's best-effort output_format parser handles a small nested schema. Asserts routing by reading the most recent Pi session jsonl rather than asking the model to self-identify (LLMs are unreliable narrators about their own identity, especially when Pi's system prompt mentions other providers as defaults). * fix(e2e-minimax-smoke): address CodeRabbit review on #1431 - Widen find window from -mmin -3 to -mmin -10. The smoke's three Pi nodes plus the assert can collectively run several minutes on slow networks; 3 minutes was tight enough to false-FAIL on a healthy run. (CodeRabbit minor) - Drop non-deterministic `head -1` over `find` output. find doesn't guarantee any order; on a tie, the wrong file would be picked. Now iterates all matching sessions and breaks on first one carrying the routing signal — any match is sufficient evidence. (CodeRabbit minor) - Replace single-regex `'"provider":"minimax".*"modelId":"MiniMax-M2.7"'` with two separate greps joined by `&&`. JSON field order isn't part of Pi's contract; a future Pi release reordering `provider` and `modelId` in the model_change event would silently false-FAIL the original pattern. The new check is order-independent. (CodeRabbit major) * fix(maintainer-review): address CodeRabbit findings on #1430 (#1432) Six findings, two majors and four minors/nitpicks: - gate.md L17 vs L77: resolved conflicting input-source instructions. Body claimed "all inline, no extra fetch" while a later phase permitted reading PULL_REQUEST_TEMPLATE.md. Now: explicit "one allowed extra read" callout in Phase 1 + matching wording in Gate C. (CodeRabbit major) - gate.md fenced blocks: added missing language identifiers (text/json/ markdown) to satisfy markdownlint MD040. (CodeRabbit minor) - gate.md L155 + read-context.ts: deterministic clock. The 3-day deadline was anchored to prior_state.last_run_at, which can be stale and produce past-dated deadlines. Moved both today and deadline_3d into the read-context.ts output (computed via sv-SE locale → ISO date in local time) and instructed the gate to use $read-context.output.deadline_3d directly. LLMs are unreliable at calendar arithmetic; this avoids it entirely. (CodeRabbit major) - maintainer-review-pr.yaml fetch-diff: dropped 2>/dev/null on gh pr diff so auth / network / deleted-PR failures fail the node instead of feeding an empty diff to the gate. Empty-but-successful diff (PR has no changes) is now an explicit marker the gate can detect. (CodeRabbit minor) - maintainer-review-pr.yaml approve-unclear: added capture_response: true so the maintainer's approve comment flows to the report node. Reject reasoning is already captured by Archon's run record. (CodeRabbit minor) - maintainer-review-pr.yaml post-decline + report.md: the gh pr edit --add-label call previously swallowed all errors with || true and the report still claimed the label was applied. Now writes applied/skipped to $ARTIFACTS_DIR/.label-applied + the gh stderr to .label-error so the report can describe the actual outcome. (CodeRabbit nitpick) * fix(workflows): approval gate bypass after reject-with-redraft on resume (#1435) * fix(workflows): approval gate bypass after reject-with-redraft on resume When an approval node was rejected with on_reject.prompt, the synthetic PromptNode built to run the on_reject prompt reused the approval gate's own node ID. executeNodeInternal then wrote a node_completed event with that ID, causing getCompletedDagNodeOutputs to treat the gate as already completed on the next resume — bypassing the human gate entirely. Fix: give the synthetic node the ID `${node.id}:on_reject` so its node_completed event has a distinct step_name that won't match the approval gate slot in priorCompletedNodes. Adds a regression test asserting no node_completed event with the approval gate's ID is written during on_reject execution. Fixes #1429 * test(workflows): add positive assertion and SSE side-effect comment for on_reject synthetic node Add complementary positive assertion to the regression test to verify that node_completed is written exactly once with step_name 'review:on_reject', ensuring future refactors that suppress the event entirely would be caught. Add inline comment in executeApprovalNode documenting the known SSE side-effect: node_started/node_completed events with nodeId='review:on_reject' flow through the SSE pipeline into the web UI, resulting in a transient phantom node in the execution view. This is cosmetic-only — the human gate contract is preserved. * simplify: reduce duplicate cast pattern in on_reject test assertions * feat(workflows): add mutates_checkout to allow concurrent runs on live checkout (#1438) * feat(workflows): add mutates_checkout field to skip path-lock for concurrent runs Add `mutates_checkout: boolean` (optional, default true) to the workflow schema. When set to false, the executor skips the path-exclusive lock that serializes all runs on the same working path, allowing N concurrent runs on the same live checkout. The primary use case is `maintainer-review-pr`, which reads shared state but writes only to per-run artifact paths and GitHub PR comments — two parallel reviews of different PRs should not fail with "Workflow already active on this path". Changes: - `schemas/workflow.ts`: add optional `mutates_checkout` field - `loader.ts`: parse and propagate the field (warn-and-ignore on invalid values) - `executor.ts`: wrap path-lock guard in `if (workflow.mutates_checkout !== false)` - `executor.test.ts`: two new tests in the concurrent-run guard suite - `maintainer-review-pr.yaml`: opt in with `mutates_checkout: false` * test(workflows): add loader tests for mutates_checkout parsing - Add 5 tests covering false, true, omitted, and invalid (string "yes") values - Invalid non-boolean values are silently dropped with warn — now explicitly tested - Remove the // end mutates_checkout guard trailing comment (no precedent in file) - Clarify loader comment: "parse/warn pattern" not "warn-and-ignore pattern" to avoid implying the return style matches interactive * simplify: collapse nodeType/aiFields pair into single nonAiNode object in parseDagNode * docs: replace String.raw with direct assignment in script node examples (#1434) * docs: replace String.raw with direct assignment in script node examples String.raw`$nodeId.output` fails silently when substituted output contains a backtick, terminating the template literal early and producing cryptic parse errors. JSON is valid JS expression syntax, so direct assignment is safe for all valid JSON values including those with backticks. - Replace String.raw pattern in dag-workflow.yaml example - Replace String.raw pattern in archon-workflow-builder.yaml template - Add CAUTION bullet in workflow-dag.md Script Node section - Add Silent Failures item #14 in parameter-matrix.md - Add Starlight caution aside in script-nodes.md - Extend script bodies bullet in variables.md - Regenerate bundled-defaults.generated.ts Fixes #1427 * docs: fix Rule 6 in generate-yaml prompt to distinguish bun vs uv patterns Rule 6 still referenced JSON.parse after the example was updated to direct assignment, creating a contradiction for the AI code generator. Update the prose to explicitly distinguish TypeScript/bun (direct assignment) from Python/uv (json.loads), matching the updated embedded example. * chore(workflows): group experimental workflows under .archon/workflows/experimental/ Move two repo-scoped workflows that were sitting untracked at the workflow root into a dedicated subfolder. Subfolder grouping is supported by the loader (1 level deep, resolution by filename), so workflow names are unchanged and the /release skill still resolves archon-release correctly. Files moved: - archon-fix-github-issue-experimental.yaml — Path-A variant of the issue-fix workflow used today to land #1434, #1435, #1438. - archon-release.yaml — the live release workflow used by the /release skill end-to-end (validate -> binary smoke -> version bump -> changelog -> approval -> commit -> PR -> tag -> Homebrew formula update). * fix(cli): handle --version, -V, -version, and lone -v as version requests Previously only the positional `version` command bypassed the git-repo check. The conventional flag aliases (--version, -V) and the single-dash typo (-version) fell through to the repo check and failed from non-git dirs. -v is the short alias for --verbose, but when it's the only argument there's no command to be verbose about, so it now falls back to version output — matching the convention used by node, npm, bun, and git. Closes #1443 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(cli): clarify why isVersionRequest is duplicated in tests Reword the JSDoc on the test-side mirror of isVersionRequest(). Previous wording ("kept in sync so test failures will catch regressions") implied an enforcement mechanism that doesn't exist — the duplication is purely manual. Documents the actual reason (cli.ts top-level main() prevents a plain import) and that maintainers must update both copies together. Addresses MEDIUM finding from #1444 comprehensive review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Rasmus Widing <152263317+Wirasm@users.noreply.github.com> Co-authored-by: Rasmus Widing <rasmus.widing@gmail.com> Co-authored-by: Cole Medin <cole@dynamous.ai> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Raphael Lechner <raphael.lechner@gmail.com> Co-authored-by: Matt Chapman <mattchapmanproductions@gmail.com> Co-authored-by: Matt Chapman <Matt@NinjitsuWeb.com>
Adds repo-triage-minimax.yaml — a Pi/Minimax M2.7 port of the existing repo-triage workflow. Identical DAG shape, state files, and guardrails; all four LLM-orchestrated nodes rewritten to do per-issue/per-PR work inline since Pi has no Task tool / inline sub-agents. Differences from the Claude variant: - Drops the four `agents:` blocks (brief-gen, closed-brief-gen, pr-brief-gen, pr-issue-matcher) — Pi can't invoke them. - Removes Task from `allowed_tools` for every prompt node. - Each "BRIEF/MATCH PASS" reads the gh JSON dump once and processes every entry inline; JSON shapes that the deleted sub-agents returned are now spec'd in the orchestrator prompt. - Skips the LABEL pass — labelling requires Claude's Task tool to invoke the on-disk triage-agent sub-agent. Run repo-triage.yaml (the Claude variant) when label-coverage matters. Validates clean: 1 valid, 0 errors, 0 warnings.
…leam00#1563) * fix(workflows): prevent zombie workflow runs from hung Pi cleanup (coleam00#1561) When a Pi/Minimax DAG node received an SDK error result, the executor threw inside the for-await loop and bridgeSession's finally hung on `await promptPromise.catch(...)` because session.dispose() does not settle session.prompt(). Bun's event loop drained with no remaining I/O sources and the process exited before the catch and DB finalization could run, leaving the workflow run stuck in 'running' indefinitely. Changes: - Wrap the post-dispose prompt-promise await in Promise.race against a 10s timeout so cleanup always completes - Add a defensive finally backstop in executeWorkflow that flips any still-'running' run to 'failed' before returning - Add regression test for the cleanup timeout - Update store mocks in executor tests to include getWorkflowRunStatus Fixes coleam00#1561 * fix: address review findings from PR coleam00#1563 - Clear the cleanup setTimeout after Promise.race resolves to prevent a dangling timer from holding the event loop open for up to 10s in fast CLI Pi workflows - Add debug log inside promptPromise.catch for Pi SDK cleanup-phase rejections so secondary errors surface without polluting output - Add warn log when the executor finally backstop fires so zombie corrections are visible in logs (not just the DB record) - Add executor finally backstop tests covering the 'running' and 'completed' status branches - Add fast-path test asserting bridgeSession cleanup completes in <1s when prompt() settles promptly (guards the clearTimeout fix) * simplify: use unknown instead of Error in backstop catch handler * fix(pi): drop blocking await on prompt promise; cleanup is non-blocking The previous fix wrapped `await promptPromise.catch(...)` in a 10s timeout race to prevent the cleanup from hanging when Pi's session.prompt() never settles after dispose(). That solved coleam00#1561 but introduced a magic number and unnecessary work. The await itself was the bug. The queue is closed before the await runs, and the .then() handlers attached to promptPromise only push to that queue — closed pushes are no-ops. There's nothing the cleanup is actually waiting for. Whether prompt() resolves in 1ms or never, no observable behavior changes for the caller. Drop the await entirely. Attach .catch() fire-and-forget so a stray async rejection (the .then() handlers should preclude this, but belt-and- suspenders) doesn't bubble up as an unhandled-rejection process exit. Cleanup now returns in <200ms regardless of Pi's prompt() behavior. Tests: - "cleanup does not block when prompt() hangs forever" — asserts <200ms (vs. the previous ~10s tolerance) - "late prompt() rejection does not become unhandled" — regression for the .catch() belt
…wn (coleam00#1529) * fix(orchestrator): create ~/.archon/workspaces before AI provider spawn On a fresh install, ~/.archon/workspaces doesn't exist yet. The orchestrator passes that path as cwd to the AI provider, which calls spawn() — which raises ENOENT. The error is then misclassified as "binary not found" in the friendly-error path, surfacing as an incorrect "Claude binary not found" message. Add ensureArchonWorkspacesPath() in @archon/paths that mkdir -p's the directory and returns the path. Use it at the orchestrator's spawn-cwd site so the directory is guaranteed to exist before spawn(). Other call sites of getArchonWorkspacesPath() (workflow discovery, path-prefix comparisons) only consume the path string and don't need the directory to exist; they keep using the pure getter. Closes coleam00#1528 * test(orchestrator): assert ensureArchonWorkspacesPath is called Capture the @archon/paths mock as a named variable and assert it was called in the syncWorkspace handleMessage path. Without this, the test suite passes even if orchestrator-agent.ts:824 reverts to the non-ensuring getArchonWorkspacesPath() variant — exactly the regression that surfaced as 'Claude Code native binary not found' in coleam00#1528.
…ion (coleam00#1575) GitHub stays the primary forge; Gitea and GitLab are community-supported targets via the existing community adapters at packages/adapters/src/community/forge/. Long-term home for outbound forge operations (PR/issue/review CRUD) is the same per-forge adapter that already handles inbound webhooks today. Closes the open direction question that has been blocking PR coleam00#1104 (forge-agnostic workflow commands) for ~8 days. The architectural cleanup — migrating PR coleam00#1104's interim forge-cli.ts into the existing community adapters — is tracked separately as coleam00#1574 and is not a precondition for merging coleam00#1104.
…de failures (coleam00#1572) * fix(pi): surface SDK error messages and cap concurrency to stop cascade failures (coleam00#1569) Parallel Pi/Minimax workflow batches degrade as concurrent session count rises: the first few succeed, the rest cascade-fail with empty review findings. Operators see only "SDK returned error" because the underlying message is discarded, which also prevents the executor's transient-retry path from classifying 429/overload errors and retrying them. Changes: - buildResultChunk now surfaces AssistantMessage.errorMessage in the result chunk's errors[] array so dag-executor's errorsDetail is populated and the thrown error message is pattern-matchable. - PiProvider gains a module-level counting semaphore configurable via assistants.pi.maxConcurrent in .archon/config.yaml. Pi has no SDK-level throttling (unlike Claude), so this is the right place to gate concurrent upstream calls. - TRANSIENT_PATTERNS now includes '529' and 'overloaded' so Anthropic's service-overload responses route to the existing retry-with-backoff path. - Tests cover errors[] population and maxConcurrent parsing. Fixes coleam00#1569 * fix(pi): address review findings — semaphore race, silent failure, test gaps - Snapshot piSemaphore into const sem before first await so the finally block only releases a slot this call actually acquired (prevents spurious over-release when concurrent calls have mixed maxConcurrent config) - Add warn log in serializeToolResult catch so serialization failures are visible rather than silently falling back to String(result) - Fix maxConcurrent JSDoc to say "Omit for unlimited" — 0 is not a valid sentinel (parsePiConfig drops it silently); remove the misleading hint - Extend errorMessage-absent test to also cover errorMessage: '' (both falsy cases excluded from errors[], now both documented) - Add combined maxConcurrent + model fields test to config.test.ts - Add classifyError tests covering 429, 529, overloaded, FATAL priority, and UNKNOWN classification for executor-shared.ts - Add semaphore smoke tests to provider.test.ts verifying initialization log and that the absent-maxConcurrent path skips initialization * simplify: remove redundant String() coercion, fix orphaned JSDoc, early-return in release()
…: false) — closes coleam00#1546 (coleam00#1555) Co-authored-by: Zolto <zolto@zhome.local>
) * Pi provider: load user settings files as session baseline Previously, the Pi provider created an empty SettingsManager.inMemory() on every query, ignoring the user's Pi settings files entirely. This meant user preferences in ~/.pi/agent/settings.json (retry counts, transport, compaction strategy, thinking budgets, default model, etc.) and per-repo overrides in <cwd>/.pi/settings.json had no effect on Archon-driven sessions. Co-authored-by: Copilot <copilot@github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * test(settings): add shallow merge behavior for object values in settings * fix(docs): correct spelling of "behavior" in Pi settings documentation * fix(tests): reset mock settings manager implementations in PiProvider tests --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…oleam00#1566) * feat(cli): setup overhaul + archon doctor + complete bundled skill (coleam00#1494) Make binary installs the official primary path. Setup wizard becomes a dead-simple "AI + skippable adapters" flow, the embedded skill ships all 21 files, and `archon doctor` provides a canonical "is my setup OK?" checklist. Changes: - bundled-skill.ts: embed all 21 .claude/skills/archon/ files (was 18, missing good-practices.md, parameter-matrix.md, troubleshooting.md) - scripts/check-bundled-skill.ts: CI guard that fails when bundled-skill.ts drifts from .claude/skills/archon/; wired into `bun run validate` - setup.ts: remove database prompt (SQLite implicit), remove Discord, validate Claude binary via spawn test, probe `gh auth status` and optionally run `gh auth login`, add Telegram security note + empty- allowlist warning, append update instructions and offer to run `archon doctor` at the end - doctor.ts: new `archon doctor` command — green/red checklist for Claude binary, gh auth, database, workspace writability, bundled defaults, and adapter token pings (best-effort) - cli.ts: register `doctor` and add to noGitCommands - doctor.test.ts: spyOn-based tests for individual check functions - setup.test.ts: drop SetupConfig.database and platforms.discord references, anchor DATABASE_URL assertion to line start Closes coleam00#1494 * fix: address review findings from PR coleam00#1566 - Fix checkWorkspaceWritable false negative: separate try/catch for rmSync so a cleanup failure never reports the directory as unwritable - Fix collectGitHubConfig empty catch: distinguish gh-not-installed (ENOENT) from gh-not-authenticated with actionable error messages - Check spawnSync('gh', ['auth', 'login']) return value and warn if spawn fails - Fix three stacked JSDoc blocks in setup.ts: restore orphaned JSDoc to collectClaudeBinaryPath and collectClaudeAuth, keep only correct block above probeClaudeBinarySpawns - Fix doctorCommand allSettled rejection handler: use String(s.reason) for non-Error rejections instead of producing "undefined" output; add log.error - Fix checkSlack/checkTelegram catch label: drop "network error: " prefix so JSON parse failures aren't mischaracterized - Fix "Skip silently" wording in doctor.ts: visible output contradicted "silently" - Fix doctor.test.ts module comment: BUNDLED_IS_BINARY is not spied, clarify - Add checkBundledDefaults test: passes in dev mode without mocks - Update CLAUDE.md: fix validate description "five" → "six" checks, add check:bundled-skill; add archon doctor to CLI section - Update docs-web cli.md: add doctor command section after setup - Add comment to check-bundled-skill.ts documenting substring check limitation * simplify: trim doctor.ts JSDoc and remove redundant existsSync check - Remove 12-line file JSDoc that restated the obvious; keep only the non-obvious setup-invocation note - Drop existsSync() guard before execFileAsync() — TOCTOU-prone and redundant since the catch block already produces a clear error - Collapse verbose allSettled comment to one line * feat(setup): bootstrap project-scoped .archon/config.yaml on skill install `archon setup` previously copied the Claude skill into the user's project but did not create a `.archon/` directory or a project config there. A fresh user who wanted any project-scoped override (per-project assistant defaults, custom docs path, etc.) had to `mkdir -p .archon` and create config.yaml by hand — exactly the friction the v0.3.11 setup overhaul was meant to remove. Add `bootstrapProjectConfig(projectPath)` and call it after the skill install. Writes a commented-out starter `.archon/config.yaml` with example keys and a link to the configuration reference. Idempotent on re-run (skips if the file already exists). Workflows/commands/scripts subdirectories are intentionally not created — empty dirs would clutter users' trees and the loaders handle their absence cleanly. The wizard's final summary now shows the created config path so the user knows where to put per-project overrides. Tests: - creates `.archon/config.yaml` when missing - creates the `.archon` directory if absent - is idempotent — leaves an existing user config untouched - returns failed state without throwing on unwritable target * fix: address multi-agent review findings from PR coleam00#1566 Production correctness: - setup.ts: gh auth login now checks .status !== 0 so a non-zero exit (cancelled OAuth, failed callback) is surfaced instead of silently succeeding. - setup.ts: probeClaudeBinarySpawns returns {ok, reason}; the warning now includes the actual spawn error (ENOENT/timeout/permissions). - setup.ts: bootstrapProjectConfig uses writeFileSync flag 'wx' and catches EEXIST, eliminating the TOCTOU window between existsSync and the write. - doctor.ts: split checkDatabase into module-load vs query try-catches so a missing @archon/core stops masquerading as "Database not reachable". Both branches now log structured errors. - doctor.ts: checkWorkspaceWritable rmSync catch logs warn so repeated delete failures leave a diagnostic trace. - cli.ts: doctor case uses a static import to match every other peer command. Tests: - checkClaudeBinary covers all four branches via an injected isBinary parameter (skip / no-path / spawn-pass / spawn-fail). - checkDatabase covers sqlite + postgres pass, query failure, and module-load failure via injectable loadDeps. - checkSlack / checkTelegram cover pass / fail / network-error->skip via spyOn(globalThis, 'fetch'). - checkGhAuth gains an explicit GH_TOKEN-only branch. - doctorCommand asserts the exit-code contract (0 on all-pass, 1 on any fail, thrown checks counted) and Promise.allSettled non-short- circuit, via injectable check thunks. Docs: - cli.md: doctor added to the no-git command list. - CLAUDE.md: validate now mentions check:bundled-skill alongside check:bundled. - overview.md: archon doctor row added to the CLI command table. - troubleshooting.md: points users at archon doctor after setup.
* feat(docs): add public roadmap page at /roadmap Adds a visual roadmap page to the docs site showing what's shipped, in progress, next, and planned — with GitHub issue links, version badges, and a secondary tier for longer-horizon items. - New `src/data/roadmap.ts` — typed data (RoadmapItem, statusConfig) for 14 items across 4 statuses and 2 tiers - New `src/pages/roadmap.astro` — standalone dark page with vertical timeline for active items, compact grid for secondary planned items, and a 2-col shipped section; no Starlight layout dependency - `astro.config.mjs` — adds Roadmap link to sidebar - `index.md` — adds Roadmap action button to hero Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(docs): remove broken /workflows/ nav link from roadmap page The nav bar linked to /workflows/ which doesn't exist in the docs site, producing a 404. Removed the dead link per review findings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Leex <thomas@thirty3.de>
…eam00#1622) * fix(workflows): add provider: claude to opus[1m] nodes — closes coleam00#1610 Without an explicit `provider:` annotation, the DAG executor falls back to `config.assistant` (default: `codex` for some users). Codex silently ignores `opus[1m]`, completes in ~3 s with empty output, and downstream nodes receive nothing. The three bundled workflow files that pair `model: opus[1m]` with no `provider:` are now annotated with `provider: claude`. Also fixes three pre-existing Windows CI issues: path-separator bug in `check-bundled-skill.ts` (backslash vs forward slash), newline truncation in `dag-executor.test.ts` script-failure test, and `IS_SANDBOX=1` guard skip in `provider.test.ts` root-UID test. Regenerates `bundled-defaults.generated.ts`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(review): address review findings for PR coleam00#1622 - Add regression tests for provider resolution (coleam00#1610): assert that a node with no provider: routes to workflowProvider, and that explicit provider: claude overrides workflowProvider even when set to 'codex' - Add bundled opus invariant test: every default workflow node with an opus model must have provider: claude at node or workflow level - Fix stale comment in dag-executor.ts:315 — null is caught by the typeof check above, not by this branch - Docs: add provider/model independence warning to authoring-workflows.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * simplify: replace for loop with .some() in isVersionRequest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…t.field works for Pi When a provider parses fence-wrapped or preamble-prefixed JSON onto the result chunk (Pi/Minimax via tryParseStructuredOutput), the executor captured it locally but never persisted it onto NodeOutput. Downstream consumers (substituteNodeOutputRefs, condition-evaluator) then JSON.parse(output)'d the original prose-prefixed text, which threw, and $node.output.field resolved to empty. This persists structuredOutput on NodeOutput (single-shot and loop-terminal-iteration success paths) and teaches both consumers to prefer the parsed object over re-parsing prose. Falls back to JSON.parse(output) when structuredOutput is absent so Claude/Codex output_format-encoded NodeOutput rows (and older rows written before this field existed) keep working. Cross-resume rehydration of structuredOutput from event_data is out of scope here; resumed runs that re-execute downstream nodes will fall through to the JSON.parse path, which matches existing behavior. Closes coleam00#1571
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
|
this seems to have way more changes then the pr body claims, please reopen once scope is fixed |
|
Rebased onto upstream/main. The branch was based off a downstream dev that carried 179 unmerged-to-main commits — the PR diff now matches the body: 5 files, +283/-9 across condition-evaluator, dag-executor, and the workflow-run schema. New SHA: 53571b9. |
Summary
tryParseStructuredOutput), the DAG executor captures it into a local variable but never persists it ontoNodeOutput. Downstream$node.output.fieldsubstitution andwhen:conditions then tryJSON.parse(output)on the original prose-prefixed text, throw, and resolve to empty.$classifier.output.type(or any other field) silently routes to the wrong branch or drops the value.structuredOutput?: unknownadded to the completed/running and failed NodeOutput branches; producer call sites persist it; consumers (substituteNodeOutputRefs,condition-evaluator.resolveOutputRef) prefer it overJSON.parse(output)and fall back to the existing path when absent.$node.output(unfielded) still readsoutputtext. Cross-resume rehydration fromevent_datais out of scope (resumed runs that re-execute downstream nodes fall through to JSON.parse). Providers that already overwriteoutputwith a JSON.stringify of structuredOutput (Claude/Codex withoutput_format) are unaffected because theiroutputis already JSON.UX Journey
Before
After
Architecture Diagram
Before
After
Connection inventory:
dag-executor(single-shot success return)NodeOutput.structuredOutputdag-executor(loop terminal-iteration return)NodeOutput.structuredOutputlastIterationStructuredOutputlocalsubstituteNodeOutputRefsNodeOutput.structuredOutputcondition-evaluator.resolveOutputRefNodeOutput.structuredOutputnodeOutputSchema(completed/running, failed)z.unknown().optional()Label Snapshot
risk: lowsize: Sworkflowsworkflows:executor,workflows:condition-evaluator,workflows:schemasChange Metadata
bugworkflowsLinked Issue
Validation Evidence (required)
New tests added:
condition-evaluator.test.ts: 12 new tests covering structuredOutput preference, Claude/Codex JSON.parse fallback regression, numeric/boolean/object/array field coercion, null/top-level-array/primitive fall-through, missing-field semantics, and bare$node.output(no field) still uses output text.dag-executor.test.ts: 13 new tests forsubstituteNodeOutputRefscovering the same matrix plus shell-escaping safety on structuredOutput fields.Security Impact (required)
NoNoNoNoCompatibility / Migration
Yes.structuredOutputis optional on the schema; producers populate it only when the provider emits one; consumers fall through to the existingJSON.parse(output)path when it is absent. Workflows that depend on the JSON.parse path keep working unchanged.NoNo. NodeOutput is stored inline in workflow event data; older rows withoutstructuredOutputdeserialize asundefinedand hit the JSON.parse fallback.NodeOutputfromevent_data. If the original run was on this version, the field is preserved; if older, it is absent and the JSON.parse fallback runs. Cross-version resume of Pi workflows that previously routed wrong will route correctly once the upstream re-runs on the new version.Human Verification (required)
$node.output(no field) ignores structuredOutput and reads output text.escapedForBash=true) wraps structuredOutput strings/objects in single quotes; numbers/booleans stay unquoted.Side Effects / Blast Radius (required)
$node.output.fieldsubstitution or awhen:expression that uses dot notation.JSON.parse(output)returning a different shape than what the provider's parsed structuredOutput contains (e.g. provider parses one JSON object out of multiple in the prose) would see a behavior change. In practice the providers that emitstructuredOutputare emitting the canonical structured payload that the prompt asked for, so the change is the intended fix, not a surprise.condition_json_parse_failed,dag_node_output_ref_json_parse_failed) still fire on the fallback path; no new error paths introduced.Rollback Plan (required)
$node.output.fieldwould resolve to JSON-stringified content where it previously resolved to a plain string (would surface as condition mismatches or wrongly-formatted prompts on Claude/Codex output_format workflows).Risks and Mitigations
$node.output.fieldon a Pi/Minimax node (because the JSON.parse failed) might now see the resolved value and take a previously-unreachable branch. This is the intended behavior change for Parsed structured output not persisted to NodeOutput — condition evaluator and $node.output.field re-parse raw text, breaking Pi/Minimax #1571 but could surprise authors who built around the bug.''when the structuredOutput object doesn't contain the requested key). The fix only changes the case where the provider actually parsed the structured payload the prompt requested.