Release 0.5.0#14
Merged
prospapledge88 merged 102 commits intomainfrom May 6, 2026
Merged
Conversation
…leam00#1017) CLI-launched workflows were visible in the Web UI chat but displayed as plain text only — no WorkflowProgressCard or WorkflowResultCard. The CLI adapter already handled both metadata fields; the sendMessage calls were simply missing from workflowRunCommand. Changes: - Send workflowDispatch message before executeWorkflow (mirrors orchestrator.ts) - Send workflowResult message after successful completion with summary - Wrap result message in try/catch with warn log (same pattern as orchestrator) Fixes coleam00#1017 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…for PR coleam00#1052 - Wrap dispatch sendMessage in try/catch (matches result card pattern) to prevent UI notification failures from blocking workflow execution - Update dispatch comment to accurately describe structural similarity to orchestrator while noting synchronous CLI semantics and that workerConversationId === conversationId in the CLI path - Add note to result card comment about paused-path exclusion - Add 4 integration tests for workflowRunCommand: dispatch ordering and metadata shape, result card with summary, no result card without summary, and non-throwing DB failure on result persist Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Template literals automatically coerce numbers to strings; wrapping with String() is redundant. Removed from formatAge, formatDuration, and all console.log calls in workflow.ts. Also compacted a two-line object spread in workflowStatusCommand to a single line. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deduplicate JSON branch in workflowStatusCommand by computing the output array once with a single console.log call, removing the duplicated verbose/non-verbose conditional branches. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply review finding: rename flat log event names to use the
cli.{action}_{state} convention matching the rest of the file.
- workflow_dispatch_surface_failed → cli.workflow_dispatch_surface_failed
- workflow_output_surface_failed → cli.workflow_result_surface_failed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aces (coleam00#1035) The cmd.exe fallback in spawnWindowsTerminal() used shell: true, which caused Bun/Node to flatten args into a single string without proper quoting. Paths with spaces were split at whitespace, breaking the /D argument to start. Changes: - Remove shell: true from cmd.exe fallback spawn options - Remove shell?: boolean from trySpawn options type (no callers need it) Fixes coleam00#1035 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…m00#1054) During SSE streaming, tool calls always appeared below all text because onText appended to the existing message even when it already had tool calls. The server-side persistence already segments at this boundary. Mirror that rule in the client's onText handler: when the last streaming message has tool calls, seal it and start a new message for incoming text. Fixes coleam00#1054 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…1055) After a workflow completes, the AI had no awareness of results when answering follow-up questions. This adds a "Recent Workflow Results" section to the orchestrator prompt by querying persisted workflow_result messages from the conversation. Changes: - Add getRecentWorkflowResultMessages() to db/messages.ts - Add WorkflowResultContext type and formatWorkflowContextSection() to prompt-builder.ts - Extend buildFullPrompt() with optional workflowContext parameter - Fetch and inject workflow context in handleMessage() before prompt building Fixes coleam00#1055 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add console.error logging to silent .catch on SSE reconnect re-fetch (ChatInterface.tsx:~544) so production failures are visible in logs - Extract onText setMessages reducer to chat-message-reducer.ts as a pure function (applyOnText) with 14 unit tests covering all 6 segmentation rules including the new tool-call boundary (issue coleam00#1054) - Refactor ChatInterface.onText to delegate to applyOnText Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Parallelize checksums + tarball fetch in serve.ts (removes waterfall latency) - Remove redundant existsSync before readFileSync in update-check.ts (catch already handles ENOENT) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…flowResultCard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CRITICAL: fix metadata filter in getRecentWorkflowResultMessages to check for workflowResult key presence instead of category (which is never persisted to DB); feature was completely non-functional on every call - HIGH: guard JSON.parse(msg.metadata) with typeof check to handle PostgreSQL JSONB columns returned as objects (not strings) by node-postgres - MEDIUM: add structured warn log inside inner metadata parse catch block - LOW: use SELECT id, content, metadata instead of SELECT * in new DB query - LOW: update comments in messages.ts and prompt-builder.ts for accuracy - Tests: add formatWorkflowContextSection unit tests (pure function coverage) - Tests: add getRecentWorkflowResultMessages tests (dialect switch + contract) - Tests: add getDatabaseType mock to messages.test.ts connection mock - Tests: add ../db/messages mock and formatWorkflowContextSection to prompt-builder mock in orchestrator-agent.test.ts - Tests: add handleMessage workflow context injection behavioral tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents "Cannot read properties of undefined (reading 'status')" crash when navigating between chat and workflow execution views during race conditions where run data may be transiently undefined. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t timeout (coleam00#1067, coleam00#1030, coleam00#1098, coleam00#1070) * fix: strip CWD .env leak, enable platform adapters in serve, add first-event timeout (coleam00#1067) Three bugs fixed: (1) Bun auto-loads CWD .env files before user code, leaking non-overlapping keys into the Archon process — new stripCwdEnv() boot import removes them before any module reads env. (2) archon serve hardcoded skipPlatformAdapters:true, preventing Slack/Telegram/Discord from starting. (3) Claude SDK query had no first-event timeout, causing silent 30-min hangs when the subprocess wedges — new withFirstMessageTimeout wrapper races the first event against a configurable deadline (default 60s). Changes: - Add @archon/paths/strip-cwd-env and strip-cwd-env-boot modules - Import boot module as first import in CLI entry point - Remove skipPlatformAdapters: true from serve.ts - Add withFirstMessageTimeout + diagnostics to ClaudeClient - Add CLAUDECODE=1 nested-session warning to CLI - Add 9 unit tests (6 strip-cwd-env + 3 timeout) Fixes coleam00#1067 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review findings for PR coleam00#1092 Fixed: - Clear setTimeout timer in withFirstMessageTimeout finally block (HIGH-1) - Add strip-cwd-env-boot to server/src/index.ts for direct dev:server path (MEDIUM-1) - Warn to stderr on non-ENOENT errors in stripCwdEnv (MEDIUM-2) - Update stale configuration.md docs for new env-loading mechanism (HIGH-2) - Add ARCHON_CLAUDE_FIRST_EVENT_TIMEOUT_MS and ARCHON_SUPPRESS_NESTED_CLAUDE_WARNING env vars to docs - Add nested Claude Code hang troubleshooting entry - Fix boot module JSDoc: "CLI and server" → "CLI" only - Fix stripCwdEnv JSDoc: remove stale "override: true" reference - Update .claude/rules/cli.md startup behavior section - Update CLAUDE.md @archon/paths description with new exports Tests added: - Assert controller.signal.aborted on timeout - Handle generator that completes immediately without yielding - Strip distinct keys from different .env files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * simplify: replace string sentinel with typed error class in withFirstMessageTimeout Replace the '__timeout__' string sentinel used to identify timeout rejections with a dedicated FirstEventTimeoutError class. instanceof checks are more explicit and robust than string comparison on error messages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address review findings — dotenv version, docs, server warning, marker strip, tests 1. Align dotenv to ^17 (was ^16, rest of monorepo uses ^17.2.3) 2. Remove incorrect SUBPROCESS_ENV_ALLOWLIST claim from docs — the SDK bypasses the env option and uses process.env directly (coleam00#1097) 3. Add CLAUDECODE=1 warning to server entry point (was only in CLI) 4. Add diagnostic payload content test for withFirstMessageTimeout 5. Integrate coleam00#1097's finding: strip CLAUDECODE + CLAUDE_CODE_* session markers (except auth vars) + NODE_OPTIONS + VSCODE_INSPECTOR_OPTIONS from process.env at entry point. Pattern-matched on CLAUDE_CODE_* prefix rather than hardcoding 6 names, so future Claude Code markers are handled automatically. Auth vars (CLAUDE_CODE_OAUTH_TOKEN, CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX) are preserved. Root cause per coleam00#1097: the Claude Agent SDK leaks process.env into the spawned child regardless of the explicit env option, so the only way to prevent the nested-session deadlock is to delete the markers from process.env at the entry point. Validation: bun run validate passes, 125 paths tests (6 new marker tests), 60 claude tests (1 new diagnostic test), DATABASE_URL leak verified stripped (target repo .env DATABASE_URL does not affect Archon DB selection). * refactor: remove SUBPROCESS_ENV_ALLOWLIST — trust user config, strip only CWD The allowlist was wrong for a single-developer tool: - It blocked keys the user intentionally set in ~/.archon/.env (ANTHROPIC_API_KEY, AWS_*, CLAUDE_CONFIG_DIR, MiniMax vars, etc.) - It was bypassed by the SDK anyway (process.env leaks to subprocess regardless of the env option — see coleam00#1097) - It attracted a constant stream of PRs adding keys (coleam00#1060, coleam00#1093, coleam00#1099) New model: CWD .env keys are the only untrusted source. stripCwdEnv() at entry point handles that. Everything in ~/.archon/.env + shell env passes through to the subprocess. No filtering, no second-guessing. Changes: - Delete env-allowlist.ts and env-allowlist.test.ts - Simplify buildSubprocessEnv() to return { ...process.env } with auth-mode logging (no token stripping — user controls their config) - Replace 4 allowlist-based tests with 1 pass-through test - Remove env-allowlist.test.ts from core test batch - Update security.md and cli.md docs to reflect the new model The CLAUDECODE + CLAUDE_CODE_* marker strip and NODE_OPTIONS strip remain in stripCwdEnv() at entry point — those are process-level safety (not per-subprocess filtering) and are needed regardless. * fix: restore override:true for archon env, add integration tests The integration tests caught a real issue: without override:true, the ~/.archon/.env load doesn't win over shell-inherited env vars. If the user's shell profile exports PORT=9999 and ~/.archon/.env has PORT=3000, the user expects Archon to use 3000. stripCwdEnv() handles CWD .env files (untrusted). override:true handles shell-inherited vars (trusted but less specific than ~/.archon/.env). Different concerns, both needed. Also adds 6 integration tests covering the full entry-point flow: 1. Global auth user with ANTHROPIC_API_KEY in CWD .env — stripped 2. OAuth token in archon env + random key in CWD — CWD stripped, archon kept 3. General leak test — nothing from CWD reaches subprocess 4. Same key in both CWD and archon — archon value wins 5. CLAUDECODE markers stripped even when not from CWD .env 6. CLAUDE_CODE_OAUTH_TOKEN survives marker strip * test: add DATABASE_URL leak scenarios to env integration tests * fix: move CLAUDECODE warning into stripCwdEnv, remove dead useGlobalAuth logic Review findings addressed: 1. CLAUDECODE warning was dead code — the boot import deleted CLAUDECODE from process.env before the warning check in cli.ts/server/index.ts could fire. Moved the warning into stripCwdEnv() itself, emitted BEFORE the deletion. Removed duplicate warning code from both entry points. 2. useGlobalAuth token stripping removed (intentional, not regression) — the old code stripped CLAUDE_CODE_OAUTH_TOKEN and CLAUDE_API_KEY when useGlobalAuth=true. Per design discussion: the user controls ~/.archon/.env and all keys they set are intentional. If they want global auth, they just don't set tokens. Simplified buildSubprocessEnv to log auth mode for diagnostics only, no filtering. 3. Docs "no override needed" corrected — cli.md and configuration.md now reflect the actual code (override: true). --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Rasmus Widing <rasmus.widing@gmail.com>
Release 0.3.6
Rename the core AI provider interface and all related types, classes, factory functions, and directory from clients/ to providers/. Rename map: - IAssistantClient → IAgentProvider - ClaudeClient → ClaudeProvider - CodexClient → CodexProvider - getAssistantClient → getAgentProvider - AssistantRequestOptions → AgentRequestOptions - IWorkflowAssistantClient → IWorkflowAgentProvider - AssistantClientFactory → AgentProviderFactory - WorkflowAssistantOptions → WorkflowAgentOptions - packages/core/src/clients/ → packages/core/src/providers/ NOT renamed (user-facing/DB-stored): assistant config key, DEFAULT_AI_ASSISTANT env var, ai_assistant_type DB column. No behavioral changes — purely naming.
…nd docs - AssistantDefaults → ProviderDefaults, ClaudeAssistantDefaults → ClaudeProviderDefaults - Logger domains: client.claude → provider.claude, client.codex → provider.codex - Fix stale JSDoc, error messages, and references in architecture docs, CHANGELOG, testing rules
- ProviderDefaults → CodexProviderDefaults (symmetric with ClaudeProviderDefaults) - Fix stale "AI client" comments in orchestrator-agent.ts and orchestrator.test.ts - Remove dead createMockAgentProvider in test/mocks/streaming.ts (zero importers, wrong method names) - Fix irregular whitespace in .claude/rules/workflows.md
…t-to-iagentprovider refactor: rename IAssistantClient to IAgentProvider
docs: consolidate Claude guidance into CLAUDE.md
…oleam00#1137) * refactor: extract providers from @archon/core into @archon/providers Move Claude and Codex provider implementations, factory, and SDK dependencies into a new @archon/providers package. This establishes a clean boundary: providers own SDK translation, core owns business logic. Key changes: - New @archon/providers package with zero-dep contract layer (types.ts) - @archon/workflows imports from @archon/providers/types — no mirror types - dag-executor delegates option building to providers via nodeConfig - IAgentProvider gains getCapabilities() for provider-agnostic warnings - @archon/core no longer depends on SDK packages directly - UnknownProviderError standardizes error shape across all surfaces Zero user-facing changes — same providers, same config, same behavior. * refactor: remove config type duplication and backward-compat re-exports Address review findings: - Move ClaudeProviderDefaults and CodexProviderDefaults to the @archon/providers/types contract layer as the single source of truth. @archon/core/config/config-types.ts now imports from there. - Remove provider re-exports from @archon/core (index.ts and types/). Consumers should import from @archon/providers directly. - Update @archon/server to depend on @archon/providers for MessageChunk. * refactor: move structured output validation into providers Each provider now normalizes its own structured output semantics: - Claude already yields structuredOutput from the SDK's native field - Codex now parses inline agent_message text as JSON when outputFormat is set, populating structuredOutput on the result chunk This eliminates the last provider === 'codex' branch from dag-executor, making it fully provider-agnostic. The dag-executor checks structuredOutput uniformly regardless of provider. Also removes the ClaudeCodexProviderDefaults deprecated alias — all consumers now use ClaudeProviderDefaults directly. * fix: address PR review — restore warnings, fix loop options, cleanup Critical fixes: - Restore MCP missing env vars user-facing warning (was silently dropped) - Restore Haiku + MCP tool search warning - Fix buildLoopNodeOptions to pass workflow-level nodeConfig (effort, thinking, betas, sandbox were silently lost for loop nodes) - Add TODO(coleam00#1135) comments documenting env-leak gate gap Cleanup: - Remove backward-compat type aliases from deps.ts (keep WorkflowTokenUsage) - Remove 26 unnecessary eslint-disable comments from test files - Trim internal helpers from providers barrel (withFirstMessageTimeout, getProcessUid, loadMcpConfig, buildSDKHooksFromYAML) - Add @archon/providers dep to CLI package.json - Fix 8 stale documentation paths pointing to deleted core/src/providers/ - Add E2E smoke test workflows for both Claude and Codex providers * fix: forward provider system warnings to users in dag-executor The dag-executor only forwarded system chunks starting with "MCP server connection failed:" — all other provider warnings (missing env vars, Haiku+MCP, structured output issues) were logged but never reached the user. Now forwards all system chunks starting with⚠️ (the prefix providers use for user-actionable warnings). * fix: add providers package to Dockerfile and fix CI module resolution - Add packages/providers/ to all three Dockerfile stages (deps, production package.json copy, production source copy) - Replace wildcard export map (./*) with explicit subpath entries to fix module resolution in CI (bun workspace linking) * chore: update bun.lock for providers package exports
…1160) * fix: make env-integration test cross-platform (Windows CI) Check for Windows env var equivalents (Path instead of PATH, USERPROFILE instead of HOME) in scenario 3 assertions. Closes coleam00#1128 * fix: Windows PATH/HOME casing in provider subprocess env test Same cross-platform fix for ClaudeProvider test — spread objects lose Windows case-insensitive behavior (Path vs PATH, USERPROFILE vs HOME).
…ies (coleam00#1162) * refactor: decompose provider sendQuery() into explicit helper boundaries (coleam00#1139) sendQuery() in both Claude and Codex providers was a monolith mixing SDK option building, nodeConfig translation, stream normalization, and error classification. This makes it hard to safely extend for Phase 2 provider extensibility. Decompose both providers into focused internal helpers: Claude: - buildBaseClaudeOptions: SDK option construction - buildToolCaptureHooks: PostToolUse/PostToolUseFailure hook setup - applyNodeConfig: workflow nodeConfig → SDK translation + structured warnings - streamClaudeMessages: raw SDK event → MessageChunk normalization - classifyAndEnrichError: error classification with retry decisions Codex: - buildTurnOptions: per-turn option construction (output schema, abort) - streamCodexEvents: raw SDK event → MessageChunk normalization - classifyAndEnrichCodexError: error classification with retry decisions Also introduces ProviderWarning { code, message } replacing raw string warnings for machine-readable provider translation warnings. Adds 43 focused unit tests covering the extracted helpers directly. Fixes coleam00#1139 * fix: export ToolResultEntry type used in public buildBaseClaudeOptions API * fix: unexport internal helpers to prevent API surface leakage, fix retry state bug Review findings: 1. Internal helpers were exported and reachable through package.json subpath exports (./claude/provider, ./codex/provider), widening the public API. All new helpers are now file-local — the only public exports remain ClaudeProvider, CodexProvider, loadMcpConfig, buildSDKHooksFromYAML, withFirstMessageTimeout, getProcessUid. 2. Codex streamState (lastTodoListSignature) was shared across retry attempts, causing todo-list dedup to suppress output on retry. Now creates fresh state per attempt. Removed direct helper test imports — existing sendQuery e2e tests (51 Claude + 42 Codex) cover all behavior paths. * fix: address review findings — abort handling, retry bugs, error swallowing Fixes from CodeRabbit + multi-agent review: 1. classifyAndEnrichError preserves first-event timeout diagnostic instead of collapsing it into generic "Query aborted" (the timeout aborts the controller, but the original error carries the coleam00#1067 breadcrumb) 2. nodeConfigWarnings emitted once before retry loop, not per attempt 3. buildSubprocessEnv() called once before retry loop (was re-logging auth mode and rebuilding { ...process.env } per attempt) 4. Abort signal listener registered once with forwarding to current controller (was accumulating per-retry listeners) 5. PostToolUse hook wrapped in try/catch (JSON.stringify can throw on circular refs — was asymmetric with PostToolUseFailure which had it) 6. Codex streamCodexEvents throws on abort instead of silent break (callers were getting truncated stream with no result/error) 7. Both providers store enrichedError (not raw error) for retry exhaustion — preserves stderr context in final throw 8. Log is_error result events at error level in Claude stream normalizer * test: add black-box behavioral tests for sendQuery decomposition fixes Restore test coverage for the specific fixes from the decomposition review, exercised through sendQuery (black-box) since helpers are file-local: Claude (6 tests): - Timeout error preserved (not collapsed into "Query aborted") - nodeConfig warnings emitted once even when retries occur - Abort signal cancels across retries via single forwarding listener - Enriched error (with stderr) thrown at retry exhaustion - PostToolUse hook handles circular reference without crashing - is_error result events logged at error level Codex (3 tests): - Abort signal throws instead of silently truncating stream - Enriched error thrown at retry exhaustion - Todo-list dedup state resets between retry attempts
- e2e-all-nodes: exercises bash, prompt, script (bun), structured output, model override (haiku), effort control, and $nodeId.output refs - e2e-mixed-providers: tests Claude + Codex in the same workflow with cross-provider output references - echo-args.js: simple script node test helper
…leam00#1135) Remove the entire env-leak scanning/consent infrastructure: scanner, allow_env_keys DB column usage, allow_target_repo_keys config, PATCH consent route, --allow-env-keys CLI flag, and UI consent toggle. The env-leak gate was the wrong primitive. Target repo .env protection is already structural: - stripCwdEnv() at boot removes Bun-auto-loaded CWD .env keys - Archon loads its own env sources afterward (~/.archon/.env) - process.env is clean before any subprocess spawns - Managed env injection (config.yaml env: + DB vars) is unchanged No scanning, no consent, no blocking. Any repo can be registered and used. Subprocesses receive the already-clean process.env.
…m00#1161) * Implement managed execution env propagation * Address managed env review feedback
…ss (coleam00#1185) * refactor: extract provider metadata seam for Phase 2 registry readiness - Add static capability constants (capabilities.ts) for Claude and Codex - Export getProviderCapabilities() from @archon/providers for capability queries without provider instantiation - Add inferProviderFromModel() to model-validation.ts, replacing three copy-pasted inline inference blocks in executor.ts and dag-executor.ts - Replace throwaway provider instantiation in dag-executor with static capability lookup (getProviderCapabilities) - Add orchestrator warning when env vars are configured but provider doesn't support envInjection * refactor: address LOW findings from code review - Remove CLAUDE_CAPABILITIES/CODEX_CAPABILITIES from public index (YAGNI — callers should use getProviderCapabilities(), not raw constants) - Remove dead _deps parameter from resolveNodeProviderAndModel and its two call-sites (no longer needed after static capability lookup refactor) - Update factory.ts module JSDoc to mention both exported functions - Add edge-case tests for getProviderCapabilities: empty string and case-sensitive throws (parity with existing getAgentProvider tests) - Add test for inferProviderFromModel with empty string (returns default, documenting the falsy-string shortcut)
The command-test node was missing allowed_tools: [], causing the Claude CLI to load full tool access. Without tools restricted, the subprocess hangs after responding. The simple prompt node with allowed_tools: [] completes in 4s — this should match. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Command nodes consistently produce zero output and hit the 30s idle timeout in CI, even with allowed_tools: []. This appears to be a bug in how command: nodes interact with the Claude CLI subprocess — the process never emits output. This adds 30s of wasted time to every run. The simple prompt node already verifies Claude connectivity. Command file discovery/loading is a deterministic operation that doesn't need an AI call to validate in a smoke test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Injects exit 1 into e2e-deterministic bash-echo node to prove the engine fix (failWorkflowRun on anyFailed) propagates to a non-zero CLI exit code and a red X in GitHub Actions. Will be reverted in the next commit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reverts the injected exit 1 in bash-echo (CI red X confirmed in run 24522356737). Removes feat/e2e-smoke-tests from branch triggers — ready to merge to dev. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Will remove feat/e2e-smoke-tests trigger in the final cleanup commit before merging to dev. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removes feat/e2e-smoke-tests from E2E workflow triggers. CI failure detection verified: red X on run 24522356737 (deliberate bash exit 1), green on run 24522484762 (reverted), and credit-exhaustion failure also correctly produced exit 1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat(ci): add E2E smoke test workflows for Claude and Codex
…am00#1262) * feat(telemetry): add anonymous PostHog workflow-invocation tracking Emits one `workflow_invoked` event per run with workflow name/description, platform, and Archon version. Uses a stable random UUID persisted to `$ARCHON_HOME/telemetry-id` for distinct-install counting, with `$process_person_profile: false` to stay in PostHog's anonymous tier. Opt out with `ARCHON_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1`. Self-host via `POSTHOG_API_KEY` / `POSTHOG_HOST`. Closes coleam00#1261 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(telemetry): stop leaking test events to production PostHog The `telemetry-id preservation` test exercised the real capture path with the embedded production key, so every `bun run validate` published a tombstone `workflow_name: "w"` event. Redirect POSTHOG_HOST to loopback so the flush fails silently; bump test timeout to accommodate the retry-then-give-up window. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(telemetry): silence posthog-node stderr leak on network failure The PostHog SDK's internal logFlushError() writes 'Error while flushing PostHog' directly to stderr via console.error on any network or HTTP error, bypassing logger config. For a fire-and-forget telemetry path this leaked stack traces to users' terminals whenever PostHog was unreachable (offline, firewalled, DNS broken, rate-limited). Pass a silentFetch wrapper to the PostHog client that masks failures as fake 200 responses. The SDK never sees an error, so it never logs. Original failure is still recorded at debug level for diagnostics. Side benefit: shutdown is now fast on network failure (no retry loop), so offline CLI commands no longer hang ~10s on exit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(telemetry): make id-preservation test deterministic Replace the fire-and-forget capture + setTimeout + POSTHOG_HOST-loopback dance with a direct synchronous call to getOrCreateTelemetryId(). Export the function with an @internal marker so tests can exercise the id path without spinning up the PostHog client. No network, no timer, no flake. Addresses CodeRabbit feedback on coleam00#1262. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…coleam00#1263) * fix(bundled-defaults): auto-generate import list, emit inline strings Root-cause fix for bundle drift (15 commands + 7 workflows previously missing from binary distributions) and a prerequisite for packaging @archon/workflows as a Node-loadable SDK. The hand-maintained `bundled-defaults.ts` import list is replaced by `scripts/generate-bundled-defaults.ts`, which walks `.archon/{commands,workflows}/defaults/` and emits a generated source file with inline string literals. `bundled-defaults.ts` becomes a thin facade that re-exports the generated records and keeps the `isBinaryBuild()` helper. Inline strings (via JSON.stringify) replace Bun's `import X from '...' with { type: 'text' }` attributes. The binary build still embeds the data at compile time, but the module now loads under Node too — removing SDK blocker #2. - Generator: `scripts/generate-bundled-defaults.ts` (+ `--check` mode for CI) - `package.json`: `generate:bundled`, `check:bundled`; wired into `validate` - `build-binaries.sh`: regenerates defaults before compile - Test: `bundle completeness` now derives expected set from on-disk files - All 56 defaults (36 commands + 20 workflows) now in the bundle * fix(bundled-defaults): address PR review feedback Review: coleam00#1263 (comment) Generator: - Guard against .yaml/.yml name collisions (previously silent overwrite) - Add early access() check with actionable error when run from wrong cwd - Type top-level catch as unknown; print only message for Error instances - Drop redundant /* eslint-disable */ emission (global ignore covers it) - Fix misleading CI-mechanism claim in header comment - Collapse dead `if (!ext) continue` guard into a single typed pass Scripts get real type-checking + linting: - New scripts/tsconfig.json extending root config - type-check now includes scripts/ via `tsc --noEmit -p scripts/tsconfig.json` - Drop `scripts/**` from eslint ignores; add to projectService file scope Tests: - Inline listNames helper (Rule of Three) - Drop redundant toBeDefined/typeof assertions; the Record<string, string> type plus length > 50 already cover them - Add content-fidelity round-trip assertion (defense against generator content bugs, not just key-set drift) Facade comment: drop dead reference to .claude/rules/dx-quirks.md. CI: wire `bun run check:bundled` into .github/workflows/test.yml so the header's CI-verification claim is truthful. Docs: CLAUDE.md step count four→five; add contributor bullet about `bun run generate:bundled` in the Defaults section and CONTRIBUTING.md. * chore(e2e): bump Codex model to gpt-5.2 gpt-5.1-codex-mini is deprecated and unavailable on ChatGPT-account Codex auth. Plain gpt-5.2 works. Verified end-to-end: - e2e-codex-smoke: structured output returns {category:'math'} - e2e-mixed-providers: claude+codex both return expected tokens
…oints
Settings → Projects Add Project only submitted { path }, so GitHub URLs
entered there failed even though the API and the Sidebar Add Project
already accepted them. Closes coleam00#1108.
Changes:
- Add packages/web/src/lib/codebase-input.ts: shared getCodebaseInput()
helper returning a discriminated { path } | { url } union (re-exported
from api.ts for convenience).
- Use the helper from all three Add Project entry points: Sidebar,
Settings, and ChatPage. Removes three divergent inline heuristics.
- SettingsPage: rename addPath → addValue (state now holds either URL
or local path) and update placeholder text.
- Tests: cover https://, git@ shorthand, ssh://, git://, whitespace,
unix/relative/home/Windows/UNC paths.
- Docs: document the unified Add Project entry point in adapters/web.md.
Heuristic flips from "assume URL unless explicitly local" to "assume
local unless explicitly remote" — only inputs starting with https?://,
ssh://, git@, or git:// are sent as { url }; everything else is sent
as { path }. The server already resolves tilde/relative paths.
Co-authored-by: Nguyen Huu Loc <lockbkbang@gmail.com>
…tings-add-project-url-support fix: accept GitHub URLs in settings add project
…andling Daily PledgeUP social content generator (9 drafts/day across Instagram, Twitter, LinkedIn) routed to Notion for human review. Runs at 05:57 AEST via CronCreate trigger. review-router and history-logger nodes both derive today's date via: TZ='Australia/Sydney' date +%Y-%m-%d rather than LLM timezone reasoning — previous inline prompt had an inverted UTC/AEST rule that stamped 2026-04-17 drafts as 2026-04-16. Adds Bash to review-router allowed_tools (history-logger already permits it via denied_tools: [Edit] scope).
On Windows, `git checkout` converts source files to CRLF via the `* text=auto` policy. The generator inlined raw file content as JSON strings, so the Windows regeneration produced `\r\n` escapes while the committed artifact (written on Linux) used `\n`. `bun run check:bundled` then flagged the file as stale and failed the Windows CI job. Fix by normalizing CRLF → LF both when reading source defaults and when comparing against the existing generated file. No-op on Linux. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Companion to 75427c7. The bundle-completeness test compared BUNDLED_* strings (now LF-normalized by the generator) against raw readFileSync output, which is CRLF on Windows checkouts. Apply the same normalization to the on-disk side so the defense-in-depth check stays meaningful on every platform. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…gex (coleam00#1256) * fix(workflows): add word boundary to context variable substitution regex (coleam00#1112) Variable substitution for $CONTEXT, $EXTERNAL_CONTEXT, and $ISSUE_CONTEXT was matching as a prefix of longer identifiers like $CONTEXT_FILE, silently corrupting bash node scripts. Added negative lookahead (?![A-Za-z0-9_]) to CONTEXT_VAR_PATTERN_STR so only exact variable names are substituted. Changes: - Add negative lookahead to CONTEXT_VAR_PATTERN_STR regex in executor-shared.ts - Add regression test for prefix-match boundary case Fixes coleam00#1112 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(workflows): add missing boundary cases for context variable substitution Add three new test cases that complete coverage of the word-boundary fix from coleam00#1112: $ISSUE_CONTEXT with suffix variants, $ISSUE_CONTEXT with multiple suffixes, and contextSubstituted=false for suffix-only prompts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Syncs 71 upstream commits covering the provider-registry refactor (IAssistantClient → IAgentProvider, @archon/core/clients extracted to new @archon/providers package), PostHog telemetry, isolation-hardening, target-repo .env-leak prevention, Claude binary-path resolver, and Telegraf → grammY migration. Fork customisations preserved: - schedules: in .archon/config.yaml (ScheduleEntry type, loader, scheduler) - workflow-analytics DB module + /api/analytics/costs route (PR #4) - archon-social-content-engine workflow - $PROJECT_KNOWLEDGE, $CONTEXT sanitisation, knowledge-writer service - cron-parser service Fork-specific behavioural changes: - Telemetry default-OFF (requires ARCHON_TELEMETRY_ENABLED=1 to opt in); upstream's opt-out env vars (ARCHON_TELEMETRY_DISABLED, DO_NOT_TRACK) still short-circuit. Tests updated to match. - Adopted upstream's "No Autonomous Lifecycle Mutation Across Process Boundaries" principle: dropped failOrphanedRuns() call at server startup (coleam00#1216). Kept startWorkflowScheduler(). Superseded by upstream: - orchestrator-agent.ts model-injection fix (now passes full assistantConfig through SendQueryOptions; Claude provider reads assistantConfig.model). Associated test additions dropped. Other: - Regenerated bundled-defaults.generated.ts from disk (36 commands, 22 workflows including our fork-only entries) - Regenerated packages/web/src/lib/api.generated.d.ts against merged server routes (includes /api/providers + /api/analytics/costs) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Post-merge cleanup: - Drop unused isAbsolute import from packages/workflows/src/dag-executor.ts (fork WIP leftover, unreferenced; caught by strict noUnusedLocals) - prettier --write packages/web/src/lib/api.generated.d.ts after regen Known pre-existing test failures (NOT caused by this merge; both reproduce on pure upstream/dev): - packages/core/src/db/connection.test.ts: 3 failures from local dev shell leaking DATABASE_URL through bun subprocess respawn. See user memory "Archon DATABASE_URL shell leak". Pass with env -i isolation. - packages/workflows/src/dag-executor.test.ts: 1 failure on the coleam00#1135 repo-subprocess-env regression test. Reproduces on clean upstream/dev; upstream CI did not catch (Bun version / env difference). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-repo skill distributor: pulls the central claude-skills repo,
diffs each shared skill against the targets declared in manifest.json,
then copies changed skills and opens a PR per target repo.
Path resolution order (most specific first):
1. $CLAUDE_SKILLS_REPO env var (explicit override)
2. ${ARCHON_HOME:-$HOME/.archon}/workspaces/claude-skills (default)
Fails fast with a clear error if the skills repo is not present at
the resolved path, matching the fork's "fail fast + explicit errors"
principle.
Also: ignore .archon/knowledge/ — runtime output of the
$PROJECT_KNOWLEDGE mechanism, not source.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore: sync upstream/dev (coleam00/archon) — 71 commits
This schedule has been moved to pledgeup-landing's own .archon/config.yaml, where it belongs. Prior placement here caused the scheduler to route dispatches to the Archon codebase worktree (schedules are scoped to the codebase that owns their config.yaml — the 'project:' field in the YAML was silently dropped by mergeRepoConfig since it's not part of ScheduleEntry). Behaviour change: - Scheduler now dispatches runs against pledgeup-landing. - Cron adjusted in the new home from `3 6 * * *` (= 16:03 AEST) to `3 20 * * *` (= 06:03 AEST winter / 07:03 AEDT summer), matching the originally stated 06:03 AEST intent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(workflows): fail loudly on SDK isError results (coleam00#1208) (coleam00#1291) Previously, `dag-executor` only failed nodes/iterations when the SDK returned an `error_max_budget_usd` result. Every other `isError: true` subtype — including `error_during_execution` — was silently `break`ed out of the stream with whatever partial output had accumulated, letting failed runs masquerade as successful ones with empty output. This is the most likely explanation for the "5-second crash" symptom in coleam00#1208: iterations finish instantly with empty text, the loop keeps going, and only the `claude.result_is_error` log tips the user off. Changes: - Capture the SDK's `errors: string[]` detail on result messages (previously discarded) and surface it through `MessageChunk.errors`. - Log `errors`, `stopReason` alongside `errorSubtype` in `claude.result_is_error` so users can see what actually failed. - Throw from both the general node path and the loop iteration path on any `isError: true` result, including the subtype and SDK errors detail in the thrown message. Note: this does not implement auto-retry. See PR comments on coleam00#1121 and the analysis on coleam00#1208 — a retry-with-fresh-session approach for loop iterations is not obviously correct until we see what `error_during_execution` actually carries in the reporter's env. This change is the observability + fail-loud step that has to come first so that signal is no longer silent. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> (cherry picked from commit 4c6ddd9) * fix(db): throw on corrupt commands JSON instead of silent empty fallback (coleam00#1033) * fix(db): throw on corrupt commands JSON instead of silent empty fallback (coleam00#967) getCodebaseCommands() silently returned {} when the commands column contained corrupt JSON. Callers had no way to distinguish 'no commands' from 'unreadable data', violating fail-fast principles. Now throws a descriptive error with the codebase ID and a recovery hint. The error is still logged for observability before throwing. Adds two test cases: corrupt JSON throws, valid JSON string parses. * fix: include parse error in log for better diagnostics (cherry picked from commit 39a05b7) * fix(isolation): raise worktree git-operation timeout to 5m (coleam00#1306) All 15 worktree git-subprocess timeouts in WorktreeProvider were hardcoded at 30000ms. Repos with heavy post-checkout hooks (lint, dependency install, submodule init) routinely exceed that budget and fail worktree creation. Consolidate them onto a single GIT_OPERATION_TIMEOUT_MS constant at 5 min. Generous enough to cover reported cases while still catching genuine hangs (credential prompts in non-TTY, stalled fetches). Chosen over the config-key approach in coleam00#1029 to avoid adding permanent .archon/config.yaml surface for a problem a raised default solves cleanly. If 5 min turns out to also be too tight for real-world use, we'll revisit. Closes coleam00#1119 Supersedes coleam00#1029 Co-authored-by: Shay Elmualem <12733941+norbinsh@users.noreply.github.com> (cherry picked from commit cc78071) * fix(web,server): show real platform connection status in Settings (coleam00#1061) The Settings page's Platform Connections section hardcoded every platform except Web to 'Not configured', so users couldn't tell whether their Slack/ Telegram/Discord/GitHub/Gitea/GitLab adapters had actually started. - Server: /api/health now returns an activePlatforms array populated live as each adapter's start() resolves. Passed into registerApiRoutes so the reference stays mutable — Telegram starts after the HTTP listener is already accepting requests, so a snapshot would miss it. - Web: SettingsPage.PlatformConnectionsSection now reads activePlatforms from /api/health and looks each platform up in a Set. Also adds Gitea and GitLab to the list (they already ship as adapters). Closes coleam00#1031 Co-authored-by: Lior Franko <liorfr@dreamgroup.com> (cherry picked from commit 08de8ee) * fix: initialize options.hooks before merging YAML node hooks (coleam00#1177) When a workflow node defines hooks (PreToolUse/PostToolUse) in YAML but no hooks exist yet on the options object, applyNodeConfig crashes with "undefined is not an object" because it tries to assign properties on the undefined options.hooks. Initialize options.hooks to {} before the merge loop. Reproduces with: archon workflow run archon-architect (which uses per-node hooks extensively). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> (cherry picked from commit 7ea3214) * fix: detect completion signal in any XML tag, not just <promise> (coleam00#1126) (coleam00#1184) * fix: detect completion signal in any XML tag, not just <promise> (coleam00#1126) Loop nodes with `until:` reported max_iterations_reached when the AI wrapped the completion signal in XML tags other than `<promise>` (e.g., `<COMPLETE>ALL_CLEAN</COMPLETE>`). The three existing regex patterns all missed this format, causing the loop to exhaust iterations and fail. Changes: - Add generic XML-wrapped signal pattern to `detectCompletionSignal` - Extend `stripCompletionTags` to strip matched XML-wrapped signals from output - Pass `loop.until` to `stripCompletionTags` call site in dag-executor - Add unit tests for detection and stripping of XML-wrapped signals - Add integration test for loop completing on final iteration with XML tags Fixes coleam00#1126 * fix: address review findings for completion signal detection - Update detectCompletionSignal JSDoc to document all three detection formats - Update stripCompletionTags JSDoc to mention the `until` parameter - Remove superfluous `m` flag from xmlWrappedPattern (no anchors, no effect) - Document that XML tag names are matched independently (intentional permissiveness) - Add test: detects signal in mismatched XML tags (permissive behavior) - Add test: strips both <promise> and XML-tagged signal in same chunk - Add assertion in DAG integration test that raw XML tags don't appear in sent messages * simplify: reduce complexity in changed files * fix: require matching XML tag names in completion-signal detection Follow-up to the initial broadening in this PR. The first version of the regex accepted mismatched open/close tags (e.g. `<COMPLETE>X</done>`) which was a small false-positive surface when the AI interleaves tags in prose. Tightens both detectCompletionSignal and stripCompletionTags to capture the tag name and enforce it on the close via \1 backreference. Case-insensitivity on the tag name is preserved. Test updates: - Flip the "permissive mismatch" case to assert strict rejection with a comment explaining the guard. - Add a case-insensitive matching case to lock that behavior in. No behavior change for workflows that use matching tags (the overwhelming common case) or for <promise>...</promise>. Behavior change is limited to the narrow "open tag and close tag disagree" case, which only happens when the AI is confused — in which case we'd rather report max_iterations_reached and let the author inspect than silently call the loop complete. (cherry picked from commit bc25dee) * fix(web): allow deleting nodes from Workflow Builder (coleam00#971) (coleam00#1113) * fix(web): allow deleting nodes from Workflow Builder (coleam00#971) Three independent gaps prevented users from deleting nodes added to the Workflow Builder canvas: dropped nodes were never auto-selected so keyboard shortcuts silently no-oped, no right-click context menu existed, and the Delete Node button was buried in the Advanced tab (hidden below the viewport for Prompt/Command, completely absent for Bash since bash nodes have no Advanced tab). Fixes coleam00#971. * fix(web): push undo snapshot before adding nodes on canvas Call onPushSnapshot() before setNodes() in both onDrop and quick-add handlers so that node additions are captured by undo/redo history. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(web): address PR coleam00#1113 review feedback - Hold nodes/edges in refs so handleNodeDeleteById and onPushSnapshot can't capture stale pre-drop state (fixes undo-stack correctness). - Clamp context-menu x/y to viewport so right-click near edges stays fully on-screen. - Drop non-conformant role=menu/menuitem from the single-item context menu; rely on the native button for accessibility. - Extend isInputTarget() to cover ARIA combobox/textbox/searchbox so Backspace in Radix/shadcn widgets never nukes a node. - Extract handleBuilderKeydown as a pure function and add tests covering the Delete/Backspace + isInputTarget invariant. - Remove issue-number references from code comments per CLAUDE.md. - Document the new delete affordances in the Workflow Builder docs. - Inline context-menu dismissal, rename pointer handler, drop unused deps in keyboardActions useMemo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> (cherry picked from commit d7f36b2) * fix(workflows): make archon-adversarial-dev sed replacement macOS-safe (coleam00#1155) * fix(workflows): make adversarial init sed portable on macOS * chore: regenerate bundled-defaults after adversarial-dev sed fix Sync generated bundle with the new temp-file sed pattern in archon-adversarial-dev.yaml so check:bundled passes and binary distributions ship the macOS-safe version. --------- Co-authored-by: laplace young <yangqk12@whu.edu.cn> Co-authored-by: Rasmus Widing <rasmus.widing@gmail.com> (cherry picked from commit 817186d) * fix(deps): override transitive axios to ^1.15.0 for CVE-2025-62718 (coleam00#1330) axios <1.15.0 can be coerced to bypass NO_PROXY rules via hostname normalization, enabling SSRF in the right network shape. Archon pulls axios transitively through @slack/bolt (^1.12.0) and @slack/web-api (^1.13.5); before this change bun.lock resolved axios@1.13.6 — within the vulnerable range. Adding "axios": "^1.15.0" to the root package.json overrides bumps the transitive resolution to axios@1.15.1 (latest compatible 1.x). Both Slack range specs accept it without API surface changes — no downstream code touches axios directly. Supersedes coleam00#1153. Credits @stefans71 for identifying and reporting the vulnerability; their PR was stale on the lockfile (0.3.5 → 0.3.6 drift on dev), so this is a fresh one-line re-do on current dev. Closes coleam00#1053. Co-authored-by: Stefans71 <stefans71@users.noreply.github.com> (cherry picked from commit ae2d936) * fix(cli): surface stale-workspace registration error instead of fake "not a git repo" (coleam00#1332) * fix(cli): surface stale-workspace registration error instead of fake "not a git repo" When workflowRunCommand auto-registers an unregistered repo, a stale ~/.archon/workspaces/<owner>/<repo>/source symlink (pointing to an old checkout) causes createProjectSourceSymlink() in @archon/paths to throw: Source symlink at <linkPath> already points to <existing>, expected <target> The CLI caught that in a try/catch, logged it at warn level, continued with `codebase = null`, and then the isolation / resume branches hit their "codebase missing" fallback and threw the generic: Cannot create worktree: not in a git repository. That message is false — the repo is valid; the Archon workspace entry is stale. It sends users down the wrong diagnostic path (checking git config, permissions, etc.) instead of pointing at the workspace dir. Fix: preserve the registration error on a new `codebaseRegistrationError` local, and at both fallback sites (resume + worktree-creation) check it before the generic "not a git repo" branch. When set, throw a truthful: Cannot {create worktree,resume}: repository registration failed. Error: <original message> Hint: Remove the stale workspace entry at <dir> and retry, or use --no-worktree to skip isolation. The hint's exact path comes from a small parser that extracts the workspace directory from the known "Source symlink at …" format; when the message shape doesn't match (future error text changes), the parser returns null and we fall back to a generic "check registration under <archon-home>/workspaces" hint — safe degradation. Regression test in workflow.test.ts asserts the new error message and negatively asserts the old "not in a git repository" string is gone. Supersedes coleam00#1157 — that PR was draft + CONFLICTING against current dev, and also mentioned Windows test-compat changes that weren't in the diff (pruned scope). This is a fresh re-do focused strictly on coleam00#1146. Closes coleam00#1146. Co-authored-by: Bortlesboat <Bortlesboat@users.noreply.github.com> * review: add resume-path test, null-fallback test, update troubleshooting docs Addresses multi-agent review feedback on this PR: - Add regression test for the --resume fallback site (the worktree-create site was already covered; the resume site had identical wiring but zero test coverage). - Add test for the unrecognized-error-shape branch of buildRegistrationFailureError so the generic workspace hint is pinned (prevents accidental inversion of the stale-entry vs generic-hint ternary). - Update the troubleshooting page to key on the new "Cannot create worktree: repository registration failed." message. Users hitting the new error won't find the page under the old heading, and the "In the future..." note is obsolete now that the error itself contains the cleanup path. - Trim both new docblocks: keep the load-bearing cross-package error string contract in extractStaleWorkspaceEntry, drop narration of what the code already shows. Drop the "Before this helper existed..." paragraph from buildRegistrationFailureError — that's CHANGELOG material. Drop PR-reference suffix from the test section divider. * review: guard getArchonHome in hint + export parser for direct tests Two follow-up fixes to the multi-agent review commit (f32f002): CodeRabbit finding — unguarded getArchonHome() in the fallback hint. If getArchonHome() ever throws (misconfigured env vars, permission issues on the resolution path), the registration-failure Error would never get constructed: we'd throw a secondary home-resolution error that masks the root cause. Wrap the fallback branch in try/catch — prefer losing the exact path in the hint over replacing the actionable registration error. A safe generic hint ("Check your Archon workspace registration and retry") takes over when getArchonHome() throws. The original error.message is always embedded verbatim in the re-thrown Error. S2 — export extractStaleWorkspaceEntry for direct table tests. The parser is where the cross-package string contract with @archon/paths actually lives; direct tests against it are cheaper than end-to-end CLI tests and pin the edge cases: - POSIX path with forward slashes (typical unix user) - Windows path with backslashes (verifies Math.max(lastIndexOf / , lastIndexOf \)) - Unrelated error message (no prefix) → null - Prefix matches but delimiter missing → null - Source path without any separator → null (guards against returning empty string, which would produce a nonsense "Remove the stale workspace entry at " hint) - Empty string → null Six new cases in the test file. The claim of Windows support in the PR description is now actually verified. * fix(test): make generic-hint assertion path-separator agnostic Windows test runner (CI) hit: Expected to contain: "Check your Archon workspace registration under /home/test/.archon/workspaces" Received: "... under \home\test\.archon\workspaces and retry, ..." path.join normalizes to `\` on Windows and `/` on POSIX. The test hardcoded forward slashes in the expected substring. Split into two separator-agnostic asserts: the prefix up to "under", then `/workspaces\b/` regex for the final path segment. Behavior doesn't change — the hint still gets the full path.join'd workspaces dir on either platform. --------- Co-authored-by: Bortlesboat <Bortlesboat@users.noreply.github.com> (cherry picked from commit 056707d) * fix(server,web,workflows): web approval gates auto-resume + reject-with-reason dialog (coleam00#1329) * fix(server,web,workflows): web approval gates auto-resume + reject-with-reason dialog Fixes three tightly-coupled bugs that made web approval gates unusable: 1. orchestrator-agent did not pass parentConversationId to executeWorkflow for any web-dispatched foreground / interactive / resumable run. Without that field, findResumableRunByParentConversation (the machinery the CLI relies on for resume) couldn't find the paused run from the same conversation on a follow-up message, and the approve/reject API handlers had no conversation to dispatch back to. 2. POST /api/workflows/runs/:runId/{approve,reject} recorded the decision and returned "Send a message to continue the workflow." — the workflow never actually resumed. Added tryAutoResumeAfterGate() that mirrors what workflowApproveCommand / workflowRejectCommand already do on the CLI: look up the parent conversation, dispatch `/workflow run <name> <userMessage>` back through dispatchToOrchestrator. Failures are non-fatal — the user can still send a manual message as a fallback. 3. The during-streaming cancel-check in dag-executor aborted any streaming node whenever the run status left 'running', including the legitimate transition to 'paused' that an approval node performs. A concurrent AI node in the same DAG layer now tolerates 'paused' and finishes its own stream; only truly terminal / unknown states (null, cancelled, failed, completed) abort the in-flight stream. Web UI: ConfirmRunActionDialog gains an optional reasonInput prop (label + placeholder) that renders a textarea and passes the trimmed value to onConfirm. WorkflowRunCard (dashboard) and WorkflowProgressCard (chat) both use it for Reject now — the chat card was still on window.confirm, which was both inconsistent with the dashboard and couldn't collect a reason. The trimmed reason threads through to $REJECTION_REASON in the workflow's on_reject prompt. Supersedes coleam00#1147. @jonasvanderhaegen surfaced the root cause and shape of the fix; that PR was 87 commits stale and pre-dated the reject-UX upgrade (coleam00#1261 area), so this is a fresh re-do on current dev. Tests: - packages/server/src/routes/api.workflow-runs.test.ts — 5 new cases: approve with parent dispatches; approve without parent returns "Send a message"; approve with deleted parent conversation skips safely; reject dispatches on-reject flows; reject that cancels (no on_reject) does NOT dispatch. - packages/core/src/orchestrator/orchestrator.test.ts — updated the two synthesizedPrompt-dispatch tests for the new executeWorkflow arity. Closes coleam00#1131. Co-authored-by: Jonas Vanderhaegen <7755555+jonasvanderhaegen@users.noreply.github.com> * fix: address multi-agent review findings for web approval auto-resume C1 (critical) — cross-adapter misrouting guard tryAutoResumeAfterGate now checks parentConv.platform_type === 'web' before dispatching. Non-web parents (Slack/Telegram/GitHub/Discord) being approved from the dashboard skip auto-resume rather than dispatching a Slack thread_ts or Telegram chat_id through the web adapter's lock manager. C2 (critical) — fire-and-forget dispatch replaced with await void dispatchToOrchestrator() meant the "Resuming workflow." response fired before async work completed, and the outer try/catch couldn't observe dispatch failures. Changed to await; response now accurately reflects dispatch outcome. I1 — replaced logPrefix string-template (which produced 3-segment api.workflow_*.dispatched event names violating {domain}.{action}_{state}) with literal event names per action, branched inside the helper. Accepts action: 'approve' | 'reject' instead. I2 — corrected misleading "foreground/interactive" qualifier in the approve-endpoint comment; background web dispatches also set parent_conversation_id via the pre-created run, so they auto-resume too. I3 — extracted shouldContinueStreamingForStatus() as a small exported policy and added 7 unit tests covering running/paused/null/cancelled/ failed/completed/unknown. Full-integration coverage of the paused- tolerance invariant would require manipulating the 10s CANCEL_CHECK_INTERVAL_MS, which is flaky-prone; unit test of the policy function captures the same invariant deterministically. I4 — updated approval-nodes.md and authoring-workflows.md to reflect that Web UI approve/reject now auto-resumes (no "send a follow-up message" copy), documented the reject-with-reason dialog and $REJECTION_REASON flow, and called out the cross-platform caveat. S1 — rewrote streaming status check as positive shouldContinue safe-list via the extracted policy function, matching the inline comment. S2 — inlined handleReject on the dashboard rather than squeezing rejectWorkflowRun through runAction with a closure; keeps runAction narrow for the single-arg lifecycle actions. S5 — new regression test covering the non-web-parent skip path (slack-platform parent → dispatch skipped → response falls back to "Send a message to continue"). S6 — removed stale reference to runAction in ConfirmRunActionDialog's onConfirm JSDoc (no longer accurate now that WorkflowProgressCard calls the dialog without runAction). S7 — fixed misleading "user can resume manually by sending any message" docstring (resume is triggered by re-running the workflow command, not by an arbitrary message). Skipped as out-of-scope: S3 — cancelWorkflowRun rowCount check (pre-existing defect; separate PR) S4 — tightening expect.anything() to UUID regex (deferred) S8 — 12-positional-arg executeWorkflow → options-bag refactor (tracked follow-up) bun run validate green locally; 68 tests in api.workflow-runs.test.ts (up from 67), 173 in dag-executor.test.ts (up from 166). * review: close I1/I2/I3/I4/I6 — paused tolerance in loop + emitter, resume test, useId I1 (loop inter-iteration check) — dag-executor.ts:1715 Used `!== 'running'` in the loop node's between-iteration status check. A sibling approval node pausing the run in the same topological layer would abort the loop mid-iteration with "Loop node '<id>' stopped at iteration N (paused)". Switched to the shared shouldContinueStreamingForStatus helper so paused is tolerated — same semantics the streaming check got. Extended inline comment explains the sibling-layer concurrency reason. I2 (skipIfStatusChanged emitter unregister) — dag-executor.ts:2886 At DAG-finalization writes the helper correctly skipped writing on any non-running state (paused included — don't mark a paused run complete), but it *also* called getWorkflowEventEmitter().unregisterRun() which broke SSE observability for a run that's still live (waiting for user approval). Split the two responsibilities: skip the write for all non-running states, but only unregister the emitter for terminal states (cancelled / deleted / completed / failed). `paused` keeps the emitter registered so resume stays visible on the dashboard. I3 (foreground_resume_detected branch untested) — orchestrator-agent.test.ts That branch was modified as part of the original fix (added parentConversationId as 11th positional arg) but no existing test configured mockFindResumableRunByParentConversation to return non-null. A positional mistake (e.g. accidentally swapping issueContext and parentConversationId) would silently break auto-resume with no failing test. New regression test configures the mock, asserts both the cwd comes from the resumable run's working_path AND parentConversationId is passed correctly at position 10. I4 (null-parent log level) — api.ts tryAutoResumeAfterGate `getConversationById` returning null is a data-integrity signal (the parent conversation was deleted while the run was paused) — worth surfacing at info level so operators notice, not hiding at debug. Missing platform_conversation_id on an existing row would be an unusual DB state and stays at debug. Added `parentDeleted: boolean` to the log context so the two cases are distinguishable in observability. I6 (hardcoded DOM id) — ConfirmRunActionDialog.tsx `id="confirm-run-action-reason"` collided when multiple dialog instances share the same page (Radix portals mitigate in practice but the code was fragile). Switched to React.useId() so each instance gets a unique id — htmlFor/id wiring preserved. S11 (arity-only assertion) — orchestrator-agent.test.ts:1092 area The interactive-workflow-on-web test asserted mockExecuteWorkflow was called, but nothing about the args. Added a specific assertion that position 10 (parentConversationId) equals 'conv-1' (the caller conversation id) — pins the wiring that I1/I2 depend on being correct. Deferred (from review S1-S10, I5, I7): - S1 (ExecuteWorkflowOptions bag) — tracked as standalone follow-up; 12 positional args with 2 adjacent optionals is a real maintenance hazard but the refactor deserves its own PR. - S7 (WHY comment on non-web else branch) — review text says the branch "correctly omits" parentConversationId but the code passes it; the combination with the web-parent guard in tryAutoResumeAfterGate is intentional. Not adding a justify-what-we-don't-do comment. - S2/S3/S4/S5/S8/S9/S10 — pure polish (event-map ternary, platformConvId inlining, shared constant for REJECTION_REASON_INPUT, onChange arrow shorthand, discriminated union, docblock trim, suffix comment drop) - I5 (soften "Resuming workflow." to "— check the dashboard for progress") — users clicking from the dashboard are already on the dashboard; the current text is accurate (enqueue completed) and concise. - I7 (test dispatch-throws path) — covered implicitly by the try/catch branch of tryAutoResumeAfterGate returning false; a direct test would require mocking handleMessage to throw and would couple to dispatchToOrchestrator internals. bun run validate green; 189 dag-executor tests, 98 orchestrator-agent tests, 68 api.workflow-runs tests — all the new cases pass. --------- Co-authored-by: Jonas Vanderhaegen <7755555+jonasvanderhaegen@users.noreply.github.com> (cherry picked from commit d5c1cd9) * feat(providers): autodetect canonical binary install paths for Claude 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. (cherry picked from commit b99cee4) * 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> (cherry picked from commit f9f8775) * fix(server): contain Discord login failure so it doesn't kill the server (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> (cherry picked from commit 5957c6e) --------- Co-authored-by: Cole Medin <cole@dynamous.ai> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Kagura <kagura.chen28@gmail.com> Co-authored-by: Rasmus Widing <152263317+Wirasm@users.noreply.github.com> Co-authored-by: Shay Elmualem <12733941+norbinsh@users.noreply.github.com> Co-authored-by: Lior Franko <lior.franko@ironsrc.com> Co-authored-by: Lior Franko <liorfr@dreamgroup.com> Co-authored-by: Alex Siri <alexsiri7@gmail.com> Co-authored-by: Ahmed <44034059+medevs@users.noreply.github.com> Co-authored-by: CauchYoung <2024302072042@whu.edu.cn> Co-authored-by: laplace young <yangqk12@whu.edu.cn> Co-authored-by: Rasmus Widing <rasmus.widing@gmail.com> Co-authored-by: Stefans71 <stefans71@users.noreply.github.com> Co-authored-by: Bortlesboat <Bortlesboat@users.noreply.github.com> Co-authored-by: Jonas Vanderhaegen <7755555+jonasvanderhaegen@users.noreply.github.com>
* fix(core/test): split connection.test.ts from DB-test batch to avoid mock pollution (coleam00#1269) messages.test.ts uses mock.module('./connection', ...) at module-load time. Per CLAUDE.md:131 (Bun issue oven-sh/bun#7823), mock.module() is process- global and irreversible. When Bun pre-loads all test files in a batch, the mock shadows the real connection module before connection.test.ts runs, causing getDatabaseType() to always return the mocked value regardless of DATABASE_URL. Move connection.test.ts into its own `bun test` invocation immediately after postgres.test.ts (which runs alone) and before the big DB/utils/ config/state batch that contains messages.test.ts. This follows the same isolation pattern already used for command-handler, clone, postgres, and path-validation tests. * fix(setup): align PORT default on 3090 across .env.example, wizard, and JSDoc (coleam00#1152) (coleam00#1271) The server's getPort() fallback changed from 3000 to 3090 in the Hono migration (coleam00#318), but .env.example, the setup wizard's generated .env, and the JSDoc describing the fallback were not updated — leaving three different sources of truth for "the default PORT." When the wizard writes PORT=3000 to ~/.archon/.env (which the Hono server loads with override: true, while Vite only reads repo-local .env), the two processes can land on different ports silently. That mismatch is the real mechanism behind the failure described in coleam00#1152. - .env.example: comment out PORT, document 3090 as the default - packages/cli/src/commands/setup.ts: wizard no longer writes PORT=3000 into the generated .env; fix the "Additional Options" note - packages/cli/src/commands/setup.test.ts: assert no bare PORT= line and the commented default is present - packages/core/src/utils/port-allocation.ts: fix stale JSDoc "default 3000" -> "default 3090" - deploy/.env.example: keep Docker default at 3000 (compose/Caddy target that) but annotate it so users don't copy it for local dev Single source of truth for the local-dev default is now basePort in port-allocation.ts. * fix(providers/claude): use || instead of ?? in hasExplicitTokens to handle empty-string env vars (coleam00#1028) Closes coleam00#1027 * chore(deps): bump claude-agent-sdk to 0.2.121, codex-sdk to 0.125.0 (coleam00#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. * fix(cli): lazy-import bundled skill files so non-setup commands don't crash on missing source (coleam00#1394) The 18 top-level `import … with { type: 'text' }` statements in `bundled-skill.ts` resolve at module load. For `bun build --compile` that's build time, so the binary embeds the strings and works regardless of any on-disk skill files. For `bun link` (linked-source) installs that's every `archon` invocation — including `archon --help`, which doesn't even use the skill content. If any of the 18 source files are missing or moved, the import fails and the CLI cannot start at all. The skill content is data the binary deploys via `archon setup`, not data the CLI needs at runtime. There's only one consumer in production code: `copyArchonSkill()` in `setup.ts`. Moving the import into that function as a dynamic import preserves the compiled-binary behavior (Bun's bundler statically analyses literal-string `import()` and embeds the chunk — verified by grepping the SKILL.md frontmatter out of a freshly compiled binary) while making the linked-source install resilient: only `archon setup` triggers the bundled-skill module load now. Verified: a known skill string appears in the compiled binary 1×, and `archon --help` no longer needs the source files to start. `copyArchonSkill()` becomes async because the dynamic import is a Promise. The single production call site is already in an async function and gets an `await`. The four `setup.test.ts` cases become async too. * fix(claude): stop passing --no-env-file to native binary in dev mode (coleam00#1461) * fix(claude): stop passing --no-env-file to native binary in dev mode The Claude Agent SDK switched from shipping `cli.js` inside the package to per-platform native binaries via optional deps somewhere in the 0.2.x series. As of 0.2.121 there is no `cli.js` in the SDK package; dev mode resolves to `@anthropic-ai/claude-agent-sdk-darwin-arm64/claude` (Mach-O). That native binary rejects `--no-env-file` with `error: unknown option '--no-env-file'` and the subprocess exits 1. `shouldPassNoEnvFile` was returning true on `cliPath === undefined` on the assumption that "dev mode = JS executable run via Bun". That assumption is dead. Tighten the predicate to only return true on an explicit `.js` suffix, so we only emit the flag when the SDK is going to spawn a Bun-runnable script. CWD `.env` leak protection is unaffected. `stripCwdEnv()` in `@archon/paths` (coleam00#1067) deletes Bun-auto-loaded `.env`/`.env.local`/ `.env.development`/`.env.production` keys from `process.env` at every Archon entry point before any subprocess is spawned. The native Claude binary does not auto-load `.env` from its cwd either. `--no-env-file` was belt-and-suspenders for the JS-via-Bun case only. Verified end-to-end with a sentinel: added a unique `ARCHON_LEAK_SENTINEL_$$` to Archon's `.env`, ran e2e-claude-smoke with a bash probe checking the subprocess env. stderr shows `[archon] stripped 23 keys from /Users/rasmus/Projects/cole/Archon (.env, .env.local)` — sentinel was deleted. Bash node prints `PASS: simple='4', no sentinel leak`. Workflow completes cleanly, no `--no-env-file` rejection from the SDK binary. bun run validate: green across all 10 packages. * fix(claude): address review on coleam00#1461 (stale docs + test gaps) Critical: file-level JSDoc at provider.ts:18 still claimed dev mode resolves cli.js. Updated to reflect SDK 0.2.x's switch to per-platform native binaries. Important: security.md still listed --no-env-file as item 2 of target-repo .env isolation. Scoped that bullet to legacy Bun-runnable JS entry points and called out that native binaries don't auto-load .env from cwd. Added an Unreleased Fixed entry to CHANGELOG.md. Updated binary-resolver.ts JSDoc title that referenced cli.js. Polish: widened the predicate to accept .mjs and .cjs (also Bun-runnable JS — matches the SDK's own internal extension list). Dropped the redundant `passesNoEnvFile` log field that mirrored `isJsExecutable`. Added unit cases for .mjs/.cjs (now true) and .ts/.tsx/.jsx (deliberately false — never SDK entry points). Added an integration test that mocks resolveClaudeBinaryPath to return a .js path and asserts executableArgs: ['--no-env-file'] flows through buildBaseClaudeOptions all the way to the SDK call — catches future regressions in the conditional spread. bun run validate: green across all 10 packages. * fix(orchestrator): clear stale session ID on error_during_execution to prevent infinite failure loop (coleam00#1294) * fix(orchestrator): clear stale session ID on error_during_execution to prevent infinite failure loop When a Claude API session expires (e.g. after container restart), the orchestrator persists the new (failed) session ID from the error result, causing every subsequent message in that conversation to hit the same error — an infinite failure loop. Fix: on error_during_execution result, set assistant_session_id to NULL instead of persisting the failed session ID. The next message starts a fresh session with full context rebuilt from the DB. Conversation history is unaffected since it lives in remote_agent_messages, independent of the Claude session. Changes: - updateSession() and tryPersistSessionId() now accept string | null - Both handleStreamMode and handleBatchMode clear session ID on error_during_execution Fixes coleam00#1280 * test(orchestrator): add stale session clearing tests + address review feedback Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com> Signed-off-by: kagura-agent <kagura.agent.ai@gmail.com> --------- Signed-off-by: kagura-agent <kagura.agent.ai@gmail.com> Co-authored-by: Claude Opus 4 (1M context) <noreply@anthropic.com> * fix(claude): honor CLAUDE_BIN_PATH in dev mode for libc-mismatch hosts (coleam00#1481) * fix(claude): honor CLAUDE_BIN_PATH in dev mode for libc-mismatch hosts The Claude Agent SDK auto-resolves its bundled native binary in [linux-x64-musl, linux-x64] order. On glibc Linux hosts (Ubuntu/Debian/ Fedora), Bun installs both via optionalDependencies and the musl variant is picked first; its ELF interpreter (/lib/ld-musl-x86_64.so.1) does not exist on glibc, so spawn fails and the SDK reports a misleading "binary not found" — the file is on disk, the loader is not. The documented escape hatch CLAUDE_BIN_PATH was dead code in dev mode: the resolver early-returned undefined when BUNDLED_IS_BINARY=false before ever reading the env var. The only workaround was patching node_modules. Move the env-var block above the BUNDLED_IS_BINARY return. Config-file path stays binary-mode-only — it's per-repo, not per-machine; env is the right knob for libc mismatches. Behavior preserved: - env unset → unchanged (undefined in dev, autodetect/throw in binary) - env set + file exists → resolved (was binary-only; now also dev) - env set + file missing → clear error (was binary-only; now also dev) Closes coleam00#1474 * chore(claude): address CodeRabbit review on coleam00#1481 - CHANGELOG entry under [Unreleased] / Fixed describing the dev-mode CLAUDE_BIN_PATH escape hatch (previously ignored). Notes that config-file path remains binary-mode-only and that env-loading + target-repo .env isolation are unchanged downstream. - Empty-string test pinning that CLAUDE_BIN_PATH='' falls through to undefined rather than throwing — protects against a future predicate typo that would treat empty as "set". - One-line note in ai-assistants.md "Binary path configuration" section pointing dev-mode users at the env-var override for the glibc/musl mismatch case. Skipped from the review: - The other two docs-page rewrites (configuration.md / troubleshooting.md): the error message itself names CLAUDE_BIN_PATH, and coleam00#1474 documents the use case publicly. One mention in ai-assistants.md is enough for discovery. - Type-style consistency tweaks in the test file: pure bikeshed. * fix(deps): bump hono to ^4.12.16 and @hono/node-server to ^1.19.13 (closes coleam00#1484) (coleam00#1499) * fix(orchestrator): create ~/.archon/workspaces before AI provider spawn (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. * docs(changelog): add Tier 1 batch 2 cherry-pick entry Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Signed-off-by: kagura-agent <kagura.agent.ai@gmail.com> Co-authored-by: Rasmus Widing <152263317+Wirasm@users.noreply.github.com> Co-authored-by: DIY Smart Code <thomas@thirty3.de> Co-authored-by: Cocoon-Break <54054995+kuishou68@users.noreply.github.com> Co-authored-by: Kagura <kagura.agent.ai@gmail.com> Co-authored-by: Claude Opus 4 (1M context) <noreply@anthropic.com> Co-authored-by: Yasser <116118149+YrFnS@users.noreply.github.com> Co-authored-by: Truffle <truffleagent@gmail.com> Co-authored-by: cjnprospa <sirhcle.j23@gmail.com>
…ch 3 (#8) * feat(workflows): inline sub-agent definitions on DAG nodes (#1276) * feat(workflows): inline sub-agent definitions on DAG nodes Add `agents:` node field letting workflow YAML define Claude Agent SDK sub-agents inline, keyed by kebab-case ID. The main agent can spawn them via the Task tool — useful for map-reduce patterns where a cheap model briefs items and a stronger model reduces. Authors no longer need standalone `.claude/agents/*.md` files for workflow-scoped helpers; the definitions live with the workflow. Claude only. Codex and community providers without the capability emit a capability warning and ignore the field. Merges with the internal `dag-node-skills` wrapper when `skills:` is also set — user-defined agents win on ID collision. * fix(workflows): address PR #1276 review feedback Critical: - Re-export agentDefinitionSchema + AgentDefinition from schemas/index.ts (matches the "schemas/index.ts re-exports all" convention). Important: - Surface user-override of internal 'dag-node-skills' wrapper: warn-level provider log + platform message to the user when agents: redefines the reserved ID alongside skills:. User-wins behavior preserved (by design) but silent capability removal is now observable. - Add validator test coverage for the agents-capability warning (codex node with agents: → warning; claude node → no warning; no-agents field → no warning). - Strengthen NodeConfig.agents duplicate-type comment explaining the intentional circular-dep avoidance and pointing at the Zod schema as authoritative source. Actual extraction is follow-up work. Simplifications: - Drop redundant typeof check in validator (schema already enforces). - Drop unreachable Object.keys(...).length > 0 check in dag-executor. - Drop rot-prone "(out of v1 scope)" parenthetical. - Drop WHAT-only comment on AGENT_ID_REGEX. - Tighten AGENT_ID_REGEX to reject trailing/double hyphens (/^[a-z0-9]+(-[a-z0-9]+)*$/). Tests: - parseWorkflow strips agents on script: and loop: nodes (parallel to the existing bash: coverage). - provider emits warn log on dag-node-skills collision; no warn on non-colliding inline agents. Docs: - Renumber authoring-workflows Summary section (12b → 13; bump 13-19). - Add Pi capability-table row for inline agents (❌, Claude-only). - Add when-to-use guidance (agents: vs .claude/agents/*.md) in the new "Inline sub-agents" section. - Cross-link skills.md Related → inline-sub-agents. - CHANGELOG [Unreleased] Added entry for #1276. * 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): 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 * 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): 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> * refactor(workflows): trust the SDK for model validation (#1463) * refactor(workflows): trust the SDK for model validation Drops cross-provider model inference and hard-coded model allow-lists. The string a workflow author writes in `model:` is forwarded to the SDK unchanged; the SDK and its API decide whether the model exists. Provider identity is the only thing Archon validates at load time — typos like `provider: claud` are caught early; everything else fails at runtime through the SDK's normal error path. Why this matters: a recent run on Sasha showed `provider: claude` + `model: opus[1m]` getting silently routed to Codex (because Codex's isModelCompatible was defined as the complement of Claude's, so anything not literally `sonnet|opus|haiku` matched). Codex then rejected the model as a `⚠️` system warning and the node "completed" in 2.1 seconds with empty output, after which the workflow opened a hallucinated PR. Three stacked bugs and two amplifiers; this commit removes all five. Changes: - Delete model-validation.ts entirely (inferProviderFromModel and isModelCompatible are gone). Drop the matching field from ProviderRegistration and from the claude/codex/pi entries. - Replace the resolver in executor.ts and dag-executor.ts (both the per-node and per-loop paths) with a flat `node.provider ?? workflow.provider ?? config.assistant`. Model never influences provider selection; load-time validation is just isRegisteredProvider on the resolved provider id. - Remove the dag-node Zod superRefine that recomputed model-compat — load-time provider validation moved to loader.ts. - Codex provider: stream loop now matches Claude's contract. error events that aren't followed by turn.completed yield `result.isError: true` (subtype `codex_stream_incomplete`) so the dag-executor's existing isError path catches them. turn.failed becomes `codex_turn_failed` with the same shape. Iterator close without a terminal event is itself a fail-stop. MCP-client errors remain filtered (Codex retries those internally). - dag-executor: AI nodes that exit the streaming loop with empty assistant text and no structured output now fail with `dag.node_empty_output` instead of completing silently — the Sasha bug's final amplifier. Bash/script/approval nodes are unaffected. Tests: model-validation.test.ts and isPiModelCompatible block deleted; codex provider tests rewritten to assert the new fail-stop contract; dag-executor empty-output test flipped to assert failure; new tests cover (a) loader rejecting unknown provider, (b) loader accepting any model string with a known provider, (c) executor passing provider+model through without re-routing, (d) executor throwing on unknown provider, (e) Codex synthesizing fail-stop on iterator close. Two cost-tracking tests adjusted to yield non-empty assistant text since their intent was cost accumulation, not empty-output handling. bun run validate: green (check:bundled, type-check, lint --max-warnings 0, format:check, all packages' test suites — 0 fail). End-to-end smoke (.archon/workflows/test-workflows/): - e2e-deterministic: PASS (engine healthy) - e2e-codex-smoke: PASS (Codex sendQuery + structured output work) - e2e-claude-smoke: FAIL with `error: unknown option '--no-env-file'` — this is a regression from the SDK 0.2.121 bump (#1460), not from this redesign. The Claude provider source is unchanged on this branch. To be fixed separately. * fix(workflows): address review on #1463 Critical: - C1: empty-output guard now skips idle-timeout completions. The on-screen message says "completed via idle timeout"; flipping that to a failure contradicted the user-facing log. Added !nodeIdleTimedOut to the guard. - C2: per-node provider identity is now validated at YAML load time. Loader iterates dagNodes after parsing and rejects any unknown provider id with "Node 'X': unknown provider 'Y'. Registered: ...". The dag-executor's runtime check stays as defense-in-depth. Important: - I1: CHANGELOG entry under [Unreleased] > Changed describing the resolver redesign + an explicit migration line for workflows that relied on cross-provider model inference. - I2: restored the dropped mockLogger.error('turn_failed') assertion in the turn.failed-without-error-message test. - I3: empty-output test now also asserts store.failWorkflowRun was called, matching the parallel error_max_budget_usd test pattern. - I4: new test that proves a node yielding zero assistant text but a valid structuredOutput is treated as a successful completion (not caught by the empty-output guard). - I5: rewrote the post-loop comment in codex/provider.ts to be precise about which dag-executor branch catches the synthesized result chunk (the throwing msg.isError branch, distinct from the empty-output guard's { state: 'failed' } return). - I6: removed PR-era "redesign" / "Sasha workflow" references from three test-file comments. - I7: docs sweep for the deleted isModelCompatible field — six files updated (CLAUDE.md, two docs guides, quick-reference, contributing guide, architecture reference). Polish: - S3: dropped the dead sawTerminal flag in streamCodexEvents — both terminal branches `return`, so reaching the post-loop block always means no terminal fired. Pure simplification. - S4: dropped parsePiModelRef and PiModelRef from community/pi/index.ts exports. The parser is consumed only by Pi's provider.ts; making it package-internal narrows the public surface. - S6: new Codex test for the bare-stream-close case (zero events, iterator just ends) — locks in the default fallback message used when no captured non-MCP error is available. - S7: new dag-executor test for per-node unknown-provider at runtime. Bypasses the loader to exercise resolveNodeProviderAndModel's throw, asserts the node_failed event carries the "unknown provider 'claud'" detail (the workflow-level fail message is a generic summary). bun run validate green across all 10 packages. * fix(workflows): address CodeRabbit review on #1463 Two real issues from CodeRabbit's automated pass on db95e8a6: 1. Empty-output fail-stop now applies to loop iterations too. The single-shot AI-node guard at executeNodeInternal only covered prompt/command nodes; executeLoopNode has its own streaming path, so a provider that closed cleanly with zero content could pause an interactive loop with a blank gate or burn the full max_iterations budget. Mirrors the contract of the single-shot guard: `fullOutput.trim() === '' && !iterationIdleTimedOut` fails the iteration with a `loop_iteration_failed` event carrying a clear error. Idle-timeout exits remain exempt for the same reason as single-shot nodes — the on-screen "completed via idle timeout" message would otherwise contradict the failure. 2. Unknown loop providers now throw instead of return-failed. The early-return path bypassed the layer dispatch's outer catch at line 2870, so loop nodes with an invalid per-node `provider:` field skipped the standard `node_failed` event, the user-facing message, and the pre-execution log entry. Throwing reuses the common failure path — same shape as resolveNodeProviderAndModel uses for non-loop nodes. Both align with CLAUDE.md's "fail fast, explicit errors, never silently swallow" principle. The third CodeRabbit finding (boundary violation for `@archon/providers` import in loader.ts) is consistent with existing precedent — `dag-executor.ts`, `executor.ts`, and `validator.ts` already import from the same path; the runtime contract (every entrypoint bootstraps the registry before parseWorkflow runs) is already enforced in tests and documented at `loader.test.ts:31`. bun run validate green across all 10 packages. * fix: ensure all PR-creating workflows target $BASE_BRANCH (#1479) - Add --base $BASE_BRANCH to gh pr create in archon-architect, archon-refactor-safely, and archon-implement-issue - Add verify-pr-base bash node to all 9 PR-creating workflows that auto-corrects via gh pr edit if the AI mis-targets - Rewire downstream depends_on edges through verify-pr-base - Regenerate bundled-defaults.generated.ts * fix(workflows): skip markdown code blocks in $nodeId.output validation (#1478) The DAG-structure validator scans `node.when`, `node.prompt`, and `loop.prompt` strings for `$nodeId.output` references. Prompt bodies in builder-style workflows embed fenced and inline code as documentation for the LLM (e.g. `archon-workflow-builder` shows how to author a script node), and those literal `$<other-node>.output` mentions were being treated as real cross-node references. Result: `archon-workflow-builder` (a bundled default) failed to load, and `bun run cli workflow run archon-workflow-builder ...` reported "references unknown node '$other-node.output'". Strip triple-backtick fenced blocks and single-backtick inline code from prompt and loop.prompt before scanning. `when:` clauses are JS-like expressions and never carry markdown code, so they pass through unchanged. Real cross-node refs in prose continue to validate. Also wraps one bare `$nodeId.output` mention in `archon-workflow-builder.yaml` Rules section in inline backticks so it reads as documentation alongside the surrounding `$nodeId.output` mentions that already use this style. Closes #1413 * fix(workflows): stop sweeping scratch artifacts from every git add -A site (#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. * fix(workflows): substitute array/object node output fields as JSON (#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, pe…
* feat(cli): add archon skill install command (#1445)
* 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 #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>
* fix(cli): handle --version, -V, -version, lone -v as version requests (#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>
* chore(changelog): document Tier 3 CLI cherry-pick batch (2 commits)
Adds a CHANGELOG entry under [Unreleased] / Fixed summarizing the two
upstream CLI commits picked in this batch:
- 4631b8e0 — archon skill install command + skill.ts refactor (#1445)
- 88d01099 — --version / -V / -version / lone -v flag handling (#1444)
Notes which CLI commits in the same chronological window were already
absorbed in earlier batches (056707d0, 7d067738) and which were deferred
(5e61faf0 setup overhaul — opinionated about Discord/database prompts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: DIY Smart Code <thomas@thirty3.de>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Thomas Ritter <thomas.ritter@crownpeak.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: Raphael Lechner <raphael.lechner@gmail.com>
Co-authored-by: Matt Chapman <mattchapmanproductions@gmail.com>
Co-authored-by: Matt Chapman <Matt@NinjitsuWeb.com>
Co-authored-by: cjnprospa <sirhcle.j23@gmail.com>
* feat(paths/cli/setup): unify env load + write on three-path model (coleam00#1302, coleam00#1303) (coleam00#1304) * feat(paths/cli/setup): unify env load + write on three-path model (coleam00#1302, coleam00#1303) Key env handling on directory ownership rather than filename. `.archon/` (at `~/` or `<cwd>/`) is archon-owned; anything else is the user's. - `<repo>/.env` — stripped at boot (guard kept), never loaded, never written - `<repo>/.archon/.env` — loaded at repo scope (wins over home), writable via `archon setup --scope project` - `~/.archon/.env` — loaded at home scope, writable via `--scope home` (default) Read side (coleam00#1302): - New `@archon/paths/env-loader` with `loadArchonEnv(cwd)` shared by CLI and server entry points. Loads both archon-owned files with `override: true`; repo scope wins. - Replaced `[dotenv@17.3.1] injecting env (0) from .env` (always lied about stripped keys) with `[archon] stripped N keys from <cwd> (...)` and `[archon] loaded N keys from <path>` lines, emitted only when N > 0. `quiet: true` passed to dotenv to silence its own output. - `stripCwdEnv` unchanged in semantics — still the only source that deletes keys from `process.env`; now logs what it did. Write side (coleam00#1303): - `archon setup` never writes to `<repo>/.env`. Writing there was incoherent because `stripCwdEnv` deletes those keys on every run. - New `--scope home|project` (default home) targets exactly one archon-owned file. New `--force` overrides the merge; backup still written. - Merge-only by default: existing non-empty values win, user-added custom keys survive, `<path>.archon-backup-<ISO-ts>` written before every rewrite. Fixes silent PostgreSQL→SQLite downgrade and silent token loss in Add mode. - One-time migration note emitted when `<cwd>/.env` exists at setup start. Tests: new `env-loader.test.ts` (6), extended `strip-cwd-env.test.ts` (+4 for the log line), extended `setup.test.ts` (+10 for scope/merge/backup/force/ repo-untouched), extended `cli.test.ts` (+5 for flag parsing). Docs: configuration.md, cli.md, security.md, cli-internals.md, setup skill — all updated to the three-path model. * fix(cli/setup): address PR review — scope/path/secret-handling edge cases - cli: resolve --scope project to git repo root so running setup from a subdir writes to <repo-root>/.archon/.env (what loadArchonEnv reads at boot), not <subdir>/.archon/.env. Fail fast with a useful message when --scope project is used outside a git repo. - setup: resolveScopedEnvPath() now delegates to @archon/paths helpers (getArchonEnvPath / getRepoArchonEnvPath) so Docker's /.archon home, ARCHON_HOME overrides, and the "undefined" literal guard all behave identically between the loader and the writer. - setup: wrap the writeScopedEnv call in try/catch so an fs exception (permission denied, read-only FS, backup copy failure) stops the clack spinner cleanly and emits an actionable error instead of a raw stack trace after the user has completed the entire wizard. - setup: checkExistingConfig(envPath?) — scope-aware existing-config read. Add/Update/Fresh now reflects the actual write target, not an unconditional ~/.archon/.env. - setup: serializeEnv escapes \r (was only \n) so values with bare CR or CRLF round-trip through dotenv.parse without corruption. Regression test added. - setup: merge path treats whitespace-only existing values (' ') as empty, so a copy-paste stray space doesn't silently defeat the wizard update for that key forever. Regression test added. - setup: 0o600 mode on the written env file AND on backup copies — writeFileSync+copyFileSync default to 0o666 & ~umask, which can leave secrets group/world-readable on a permissive umask. - docs/cli.md + setup skill: appendix sections that still described the pre-coleam00#1303 two-file symlink model now reflect the three-path model. * fix(paths/env-loader): Windows-safe assertion for home-scope load line The test asserted the log line contained `from ~/`, which is opportunistic tilde-shortening that only happens when the tmpdir lives under `homedir()`. On Windows CI the tmpdir is on `D:\\` while homedir is `C:\\Users\\...`, so the path renders absolute and the `~/` never appears. Match on the count and the archon-home tmpdir segment instead — robust on both Unix tilde-short paths and Windows absolute paths. * feat(paths,workflows): unify ~/.archon/{workflows,commands,scripts} + drop globalSearchPath (closes coleam00#1136) (coleam00#1315) * feat(paths,workflows): unify ~/.archon/{workflows,commands,scripts} + drop globalSearchPath Collapses the awkward `~/.archon/.archon/workflows/` convention to a direct `~/.archon/workflows/` child (matching `workspaces/`, `archon.db`, etc.), adds home-scoped commands and scripts with the same loading story, and kills the opt-in `globalSearchPath` parameter so every call site gets home-scope for free. Closes coleam00#1136 (supersedes @jonasvanderhaegen's tactical fix — the bug was the primitive itself: an easy-to-forget parameter that five of six call sites on dev dropped). Primitive changes: - Home paths are direct children of `~/.archon/`. New helpers in `@archon/paths`: `getHomeWorkflowsPath()`, `getHomeCommandsPath()`, `getHomeScriptsPath()`, and `getLegacyHomeWorkflowsPath()` (detection-only for migration). - `discoverWorkflowsWithConfig(cwd, loadConfig)` reads home-scope internally. The old `{ globalSearchPath }` option is removed. Chat command handler, Web UI workflow picker, orchestrator resolve path — all inherit home-scope for free without maintainer patches at every new site. - `discoverScriptsForCwd(cwd)` merges home + repo scripts (repo wins on name collision). dag-executor and validator use it; the hardcoded `resolve(cwd, '.archon', 'scripts')` single-scope path is gone. - Command resolution is now walked-by-basename in each scope. `loadCommand` and `resolveCommand` walk 1 subfolder deep and match by `.md` basename, so `.archon/commands/triage/review.md` resolves as `review` — closes the latent bug where subfolder commands were listed but unresolvable. - All three (`workflows/`, `commands/`, `scripts/`) enforce a 1-level subfolder cap (matches the existing `defaults/` convention). Deeper nesting is silently skipped. - `WorkflowSource` gains `'global'` alongside `'bundled'` and `'project'`. Web UI node palette shows a dedicated "Global (~/.archon/commands/)" section; badges updated. Migration (clean cut — no fallback read): - First use after upgrade: if `~/.archon/.archon/workflows/` exists, Archon logs a one-time WARN per process with the exact `mv` command: `mv ~/.archon/.archon/workflows ~/.archon/workflows && rmdir ~/.archon/.archon` The legacy path is NOT read — users migrate manually. Rollback caveat noted in CHANGELOG. Tests: - `@archon/paths/archon-paths.test.ts`: new helper tests (default HOME, ARCHON_HOME override, Docker), plus regression guards for the double-`.archon/` path. - `@archon/workflows/loader.test.ts`: home-scoped workflows, precedence, subfolder 1-depth cap, legacy-path deprecation warning fires exactly once per process. - `@archon/workflows/validator.test.ts`: home-scoped commands + subfolder resolution. - `@archon/workflows/script-discovery.test.ts`: depth cap + merge semantics (repo wins, home-missing tolerance). - Existing CLI + orchestrator tests updated to drop `globalSearchPath` assertions. E2E smoke (verified locally, before cleanup): - `.archon/workflows/e2e-home-scope.yaml` + scratch repo at /tmp - Home-scoped workflow discovered from an unrelated git repo - Home-scoped script (`~/.archon/scripts/*.ts`) executes inside a script node - 1-level subfolder workflow (`~/.archon/workflows/triage/*.yaml`) listed - Legacy path warning fires with actionable `mv` command; workflows there are NOT loaded Docs: `CLAUDE.md`, `docs-web/guides/global-workflows.md` (full rewrite for three-type scope + subfolder convention + migration), `docs-web/reference/ configuration.md` (directory tree), `docs-web/reference/cli.md`, `docs-web/guides/authoring-workflows.md`. Co-authored-by: Jonas Vanderhaegen <7755555+jonasvanderhaegen@users.noreply.github.com> * test(script-discovery): normalize path separators in mocks for Windows The 4 new tests in `scanScriptDir depth cap` and `discoverScriptsForCwd — merge repo + home with repo winning` compared incoming mock paths with hardcoded forward-slash strings (`if (path === '/scripts/triage')`). On Windows, `path.join('/scripts', 'triage')` produces `\scripts\triage`, so those branches never matched, readdir returned `[]`, and the tests failed. Added a `norm()` helper at module scope and wrapped the incoming `path` argument in every `mockImplementation` before comparing. Stored paths go through `normalizeSep()` in production code, so the existing equality assertions on `script.path` remain OS-independent. Fixes Windows CI job `test (windows-latest)` on PR coleam00#1315. * address review feedback: home-scope error handling, depth cap, and tests Critical fixes: - api.ts: add `maxDepth: 1` to all 3 findMarkdownFilesRecursive calls in GET /api/commands (bundled/home/project). Without this the UI palette surfaced commands from deep subfolders that the executor (capped at 1) could not resolve — silent "command not found" at runtime. - validator.ts: wrap home-scope findMarkdownFilesRecursive and resolveCommandInDir calls in try/catch so EACCES/EPERM on ~/.archon/commands/ doesn't crash the validator with a raw filesystem error. ENOENT still returns [] via the underlying helper. Error handling fixes: - workflow-discovery.ts: maybeWarnLegacyHomePath now sets the "warned-once" flag eagerly before `await access()`, so concurrent discovery calls (server startup with parallel codebase resolution) can't double-warn. Non-ENOENT probe errors (EACCES/EPERM) now log at WARN instead of DEBUG so permission issues on the legacy dir are visible in default operation. - dag-executor.ts: wrap discoverScriptsForCwd in its own try/catch so an EACCES on ~/.archon/scripts/ routes through safeSendMessage / logNodeError with a dedicated "failed to discover scripts" message instead of being mis-attributed by the outer catch's "permission denied (check cwd permissions)" branch. Tests: - load-command-prompt.test.ts (new): 6 tests covering the executor's command resolution hot path — home-scope resolves when repo misses, repo shadows home, 1-level subfolder resolvable by basename, 2-level rejected, not-found, empty-file. Runs in its own bun test batch. - archon-paths.test.ts: add getHomeScriptsPath describe block to match the existing getHomeCommandsPath / getHomeWorkflowsPath coverage. Comment clarity: - workflow-discovery.ts: MAX_DISCOVERY_DEPTH comment now leads with the actual value (1) before describing what 0 would mean. - script-discovery.ts: copy the "routing ambiguity" rationale from MAX_DISCOVERY_DEPTH to MAX_SCRIPT_DISCOVERY_DEPTH. Cleanup: - Remove .archon/workflows/e2e-home-scope.yaml — one-off smoke test that would ship permanently in every project's workflow list. Equivalent coverage exists in loader.test.ts. Addresses all blocking and important feedback from the multi-agent review on PR coleam00#1315. --------- Co-authored-by: Jonas Vanderhaegen <7755555+jonasvanderhaegen@users.noreply.github.com> * feat(isolation,workflows): worktree location + per-workflow isolation policy (coleam00#1310) * feat(isolation): per-project worktree.path + collapse to two layouts Adds an opt-in `worktree.path` to .archon/config.yaml so a repo can co-locate worktrees with its own checkout (`<repoRoot>/<path>/<branch>`) instead of the default `~/.archon/workspaces/<owner>/<repo>/worktrees/<branch>`. Requested in joelsb's coleam00#1117. Primitive changes (clean up the graveyard rather than add parallel code paths): - Collapse worktree layouts from three to two. The old "legacy global" layout (`~/.archon/worktrees/<owner>/<repo>/<branch>`) is gone — every repo resolves to the workspace-scoped layout (`~/.archon/workspaces/<owner>/<repo>/worktrees/<branch>`), whether it was archon-cloned or locally registered. `extractOwnerRepo()` on the repo path is the stable identity fallback. Ends the divergence where workspace-cloned and local repos had visibly different worktree trees. - `getWorktreeBase()` in @archon/git now returns `{ base, layout }` and accepts an optional `{ repoLocal }` override. The layout value replaces the old `isProjectScopedWorktreeBase()` classification at the call sites (`isProjectScopedWorktreeBase` stays exported as deprecated back-compat). - `WorktreeCreateConfig.path` carries the validated override from repo config. `resolveRepoLocalOverride()` fails loudly on absolute paths, `..` escapes, and resolve-escape edge cases (Fail Fast — no silent default fallback when the config is syntactically wrong). - `WorktreeProvider.create()` now loads repo config exactly once and threads it through `getWorktreePath()` + `createWorktree()`. Replaces the prior swallow-then-retry pattern flagged on coleam00#1117. `generateEnvId()` is gone — envId is assigned directly from the resolved path (the invariant was already documented on `destroy(envId)`). Tests (packages/git + packages/isolation): - Update the pre-existing `getWorktreeBase` / `isProjectScopedWorktreeBase` suite for the new two-layout return shape and precedence. - Add 8 tests for `worktree.path`: default fallthrough, empty/whitespace ignored, override wins for workspace-scoped repos, rejects absolute, rejects `../` escapes (three variants), accepts nested relative paths. Docs: add `worktree.path` to the repo config reference with explicit precedence and the `.gitignore` responsibility note. Co-authored-by: Joel Bastos <joelsb2001@gmail.com> * feat(workflows): per-workflow worktree.enabled policy Introduces a declarative top-level `worktree:` block on a workflow so authors can pin isolation behavior regardless of invocation surface. Solves the case where read-only workflows (e.g. `repo-triage`) should always run in the live checkout, without every CLI/web/scheduled-trigger caller having to remember to set the right flag. Schema (packages/workflows/src/schemas/workflow.ts + loader.ts): - New optional `worktree.enabled: boolean` on `workflowBaseSchema`. Loader parses with the same warn-and-ignore discipline used for `interactive` and `modelReasoningEffort` — invalid shapes log and drop rather than killing workflow discovery. Policy reconciliation (packages/cli/src/commands/workflow.ts): - Three hard-error cases when YAML policy contradicts invocation flags: • `enabled: false` + `--branch` (worktree required by flag, forbidden by policy) • `enabled: false` + `--from` (start-point only meaningful with worktree) • `enabled: true` + `--no-worktree` (policy requires worktree, flag forbids it) - `enabled: false` + `--no-worktree` is redundant, accepted silently. - `--resume` ignores the pinned policy (it reuses the existing run's worktree even when policy would disable — avoids disturbing a paused run). Orchestrator wiring (packages/core/src/orchestrator/orchestrator-agent.ts): - `dispatchOrchestratorWorkflow` short-circuits `validateAndResolveIsolation` when `workflow.worktree?.enabled === false` and runs directly in `codebase.default_cwd`. Web chat/slack/telegram callers have no flag equivalent to `--no-worktree`, so the YAML field is their only control. - Logged as `workflow.worktree_disabled_by_policy` for operator visibility. First consumer (.archon/workflows/repo-triage.yaml): - `worktree: { enabled: false }` — triage reads issues/PRs and writes gh labels; no code mutations, no reason to spin up a worktree per run. Tests: - Loader: parses `worktree.enabled: true|false`, omits block when absent. - CLI: four new integration tests for the reconciliation matrix (skip when policy false, three hard-error cases, redundant `--no-worktree` accepted, `--no-worktree` + `enabled: true` rejected). Docs: authoring-workflows.md gets the new top-level field in the schema example with a comment explaining the precedence and the `enabled: true|false` semantics. * fix(isolation): use path.sep for repo-containment check on Windows resolveRepoLocalOverride was hardcoding '/' as the separator in the startsWith check, so on Windows (where `resolve()` returns backslash paths like `D:\Users\dev\Projects\myapp`) every otherwise-valid relative `worktree.path` was rejected with "resolves outside the repo root". Fixed by importing `path.sep` and using it in the sentinel. Fixes the 3 Windows CI failures in `worktree.path repo-local override`. --------- Co-authored-by: Joel Bastos <joelsb2001@gmail.com> * docs(worktree): fix stale rename example + document copyFiles properly (coleam00#1328) Three related fixes around the `worktree.copyFiles` primitive: 1. Remove the `.env.example -> .env` rename example from reference/configuration.md and getting-started/overview.md. The `->` parser was removed in coleam00#739 (2026-03-19) because it caused the stale-credentials production bug in coleam00#228 — but the docs kept advertising it. A user writing `.env.example -> .env` today gets `parseCopyFileEntry` returning `{source: '.env.example -> .env', destination: '.env.example -> .env'}`, stat() fails with ENOENT, and the copy silently no-ops at debug level. 2. Replace the single-line "Default behavior: .archon/ is always copied" note with a proper "Worktree file copying" subsection that explains: - Why this exists (git worktree add = tracked files only; gitignored workflow inputs need this hook) - The `.archon/` default (no config needed for the common case) - Common entries: .env, .vscode/, .claude/, plans/, reports/, data fixtures - Semantics: source=destination, ENOENT silently skipped, per-entry error isolation, path-traversal rejected - Interaction with `worktree.path` (both layouts get the same treatment) 3. Update the overview example to drop the `.env.example + .env` pair (which implied rename semantics) in favor of `.env + plans/`, and call out that `.archon/` is auto-copied so users don't list it. No code changes. `bun run format:check` and `bun run lint` green. * fix(workflows): archon-assist runs in live checkout (worktree.enabled: false) — closes coleam00#1546 (coleam00#1555) Co-authored-by: Zolto <zolto@zhome.local> * chore(changelog): document Tier 4 paths/env unification cherry-pick batch (5 commits) Adds a CHANGELOG entry under [Unreleased] / Fixed summarizing the five upstream commits picked in this batch: - 28908f0 — env load/write three-path model + loadArchonEnv helper (coleam00#1302/coleam00#1303/coleam00#1304) - 7be4d0a — unify ~/.archon/{workflows,commands,scripts} (coleam00#1315) - 5ed38dc — worktree.path config + per-workflow worktree.enabled policy (coleam00#1310) - ba4b9b4 — docs follow-up to 5ed38dc (coleam00#1328) - e33e0de — archon-assist worktree.enabled: false (deferred from PR #8, now unblocked) Notes that cc78071 (worktree timeout 5m) was already absorbed in earlier batches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Rasmus Widing <152263317+Wirasm@users.noreply.github.com> Co-authored-by: Jonas Vanderhaegen <7755555+jonasvanderhaegen@users.noreply.github.com> Co-authored-by: Joel Bastos <joelsb2001@gmail.com> Co-authored-by: ztech-gthb <ztech-001@gmx.net> Co-authored-by: Zolto <zolto@zhome.local> Co-authored-by: cjnprospa <sirhcle.j23@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 6 (#11) * docs/skill: general hardening — fix inaccuracies, fill workflow/CLI/env 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 #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. * 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. * feat(cli): setup overhaul + archon doctor + complete bundled skill (coleam00#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. * chore(changelog): document Tier 5 setup overhaul cherry-pick batch (3 commits) Adds CHANGELOG entries under [Unreleased]: - Changed: setup wizard simplified to AI + skippable adapters (notes Discord/Postgres still runtime-supported via .env, just dropped from the interactive wizard). - Fixed: 3 commits picked - 2c15439 — skill docs hardening (good-practices.md, troubleshooting.md, expanded workflow-dag.md) - 9122673 — parameter-matrix.md reference - 5e61faf — archon doctor + setup overhaul + complete bundled skill (coleam00#1566, deferred from PR #4) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Rasmus Widing <152263317+Wirasm@users.noreply.github.com> Co-authored-by: Cole Medin <cole@dynamous.ai> Co-authored-by: cjnprospa <sirhcle.j23@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h 7 (#12) * fix(workflows): make archon-adversarial-dev sed replacement macOS-safe (coleam00#1155) * fix(workflows): make adversarial init sed portable on macOS * chore: regenerate bundled-defaults after adversarial-dev sed fix Sync generated bundle with the new temp-file sed pattern in archon-adversarial-dev.yaml so check:bundled passes and binary distributions ship the macOS-safe version. --------- Co-authored-by: laplace young <yangqk12@whu.edu.cn> Co-authored-by: Rasmus Widing <rasmus.widing@gmail.com> * fix(workflows): filter user-plugin MCP noise out of workflow warnings (coleam00#1327) * fix(workflows): filter user-plugin MCP noise out of workflow warnings Before this change, the dag-executor surfaced every entry in the Claude SDK's "MCP server connection failed: …" system message to the user. That message includes user-level plugin MCPs inherited from ~/.claude/ (e.g. `telegram`) that fail to connect in the headless workflow subprocess — they're non-actionable noise for the workflow author. Fix: - Pre-compute the set of workflow-configured MCP server names per node by parsing the `mcp:` config file once at the start of executeNodeInternal. No caller-facing API change; no duplication of the provider's env-var expansion logic (we only need the keys). - Split the system-message handler: the `MCP server connection failed:` path now surfaces only the subset of failing names that match the node's configured set; user-plugin failures are debug-logged as `dag.mcp_plugin_connection_suppressed`. The `⚠️ ` branch is unchanged. Supersedes coleam00#1134 (closed as stale — the Windows HOME fix in that PR was already shipped via coleam00#1302, and the claude.ts enabledPlugins change targeted a file that has since moved into @archon/providers). Credits @MrFadiAi for identifying and reporting the underlying issue. Co-authored-by: Fadi Ai <MrFadiAi@users.noreply.github.com> * fix(workflows): preserve MCP failure status in filtered message + observability Address review feedback on PR coleam00#1327: - parseMcpFailureServerNames now returns {name, segment} entries so the forwarded "MCP server connection failed: ..." message preserves the per-server status detail (e.g. "(timeout)", "(disconnected)") that the bare-name reconstruction was dropping. - loadConfiguredMcpServerNames now debug-logs read failures as dag.mcp_filter_config_read_failed instead of swallowing them silently. A transient EMFILE/EBUSY at filter time would otherwise silently reclassify a real workflow-MCP failure as plugin noise. - Add 4 integration tests through executeDagWorkflow covering the mixed workflow/plugin split, all-plugin suppression, no-mcp:-config nodes, and the unchanged⚠️ warning path. - Drop a WHAT comment above configuredMcpNames and a temporal phrase ("before this filter landed") that would rot on merge. - Document the filtering boundary in guides/mcp-servers.md and add a troubleshooting row for users debugging silently-suppressed plugin MCPs. --------- Co-authored-by: Fadi Ai <MrFadiAi@users.noreply.github.com> * test(workflows): add anyFailed status derivation coverage for DAG executor (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. * fix(workflow): migrate piv-loop plan handoff to $ARTIFACTS_DIR (coleam00#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> * chore(workflows): switch default Opus pin to opus[1m] alias (coleam00#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(workflows): approval gate bypass after reject-with-redraft on resume (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 * fix(workflows): concise failure messages for bash/script nodes (coleam00#1389) (coleam00#1393) * fix(workflows): concise failure messages for bash/script nodes (coleam00#1389) When a `bash:` or `script:` node fails, the executor was embedding `err.message` verbatim into the user-visible error. For inline scripts run via `bash -c <body>` / `bun -e <body>`, Node's `promisify(execFile)` puts the entire substituted script body into `err.message`, `err.cmd`, and the first line of `err.stack` — and the Pino log serialized all three, repeating the body ~3× in one structured log line. The actionable diagnostic was buried under kilobytes of echoed source. Route both catch-block default branches through a new `formatSubprocessFailure(err, label)` helper in `executor-shared.ts` that: - strips the `Command failed: <cmd>` prefix line (which carries the body) - prefers `err.stderr` — Bun/bash emit the actionable diagnostic there - tail-truncates at 2 KB with a `…[truncated]` marker - returns a controlled `logFields` subset (`exitCode`, `killed`, `stderrTail`) so Pino never re-serializes `err.message` / `err.stack` / `err.cmd` Also drops the script-node `stderrHint` concatenation — stderr is already handled by the helper, so the previous code appended it twice. Timeout / ENOENT / EACCES branches are preserved verbatim. Fixes coleam00#1389 * fix(workflows): address PR review feedback for coleam00#1389 - Run formatSubprocessFailure unconditionally so timeout / ENOENT / EACCES branches also get sanitized log fields (the timeout message also embeds the `Command failed: bash -c <body>` line). - Drop `errType: err.constructor.name` (always 'Error' in production) and replace with `nodeType: 'bash' | 'script'` for actual discriminating value. - Replace chained `||` + ternary in diagnostic selection with explicit if/else for readability. - Simplify exit suffix guard: `err.code != null` instead of double typeof. - Make stderrTail emptiness check explicit. - Drop `RawSubprocessError` export (no external consumer) and widen `code` to `number | string | null` to mirror Node's ExecFileException. - Tighten test length bound to <2100 (was <4000 / <2200) so a doubling of SUBPROCESS_ERROR_MAX_CHARS would actually trip the assertion. - Replace broad regex assertion with `.toContain('[eval]')` — the location marker is the strongest signal that diagnostic content survived. - Strip issue-number citations from describe block and test comments. - Update script-nodes.md to distinguish stderr behavior on success vs failure paths. - Add CHANGELOG Unreleased / Fixed entry for the user-visible change. * chore(workflows): regenerate bundled defaults after Tier 6 picks * docs: add CHANGELOG entry for Tier 6 workflow polish batch --------- Co-authored-by: CauchYoung <2024302072042@whu.edu.cn> Co-authored-by: laplace young <yangqk12@whu.edu.cn> Co-authored-by: Rasmus Widing <rasmus.widing@gmail.com> Co-authored-by: Rasmus Widing <152263317+Wirasm@users.noreply.github.com> Co-authored-by: Fadi Ai <MrFadiAi@users.noreply.github.com> Co-authored-by: Cole Medin <cole@dynamous.ai> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: cjnprospa <sirhcle.j23@gmail.com>
* chore(gitignore): ignore .claude/scheduled_tasks.lock Machine-local runtime state from the Claude Code scheduler (pid + sessionId + acquisition timestamp). Should not be shared across machines. * fix(docs): prevent theme reset to dark after user switches to auto/light (coleam00#1079) Starlight removes the `starlight-theme` localStorage key when the user selects "auto" mode. The old init script checked that key, so every navigation or refresh re-forced dark theme. Use a separate `archon-theme-init` sentinel that persists across theme changes. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add CHANGELOG entry for batch 8 sweep-up --------- Co-authored-by: Rasmus Widing <rasmus.widing@gmail.com> Co-authored-by: Fly Lee <164855381+lifeiyu071@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: cjnprospa <sirhcle.j23@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release 0.5.0
Catches the fork up to
coleam00/archonupstream/dev (49 upstream commits across 8 cherry-pick batches: workflow polish, providers, web UI, db, reliability, paths/env unification, setup overhaul). Plus fork-local work: provider extraction into@archon/providers, prompt-injection defense, cost analytics, scheduled workflows, and security hardening (CWD.envstrip, axios CVE-2025-62718 override).Cherry-pick batches landed in this release
Cumulative upstream absorption: 59 / 110. Remaining gap is dominantly non-applicable to fork (Pi, maintainer workflows, Docker, release/homebrew machinery the fork doesn't ship).
Headline changes (full detail in
CHANGELOG.md)Changed
.env)CLAUDE_BIN_PATHorassistants.claude.claudeBinaryPathrequired in compiled buildsAdded
archon doctorcommand (green/red checklist)archon skill installstandalone subcommand$LOOP_PREV_OUTPUTand$PROJECT_KNOWLEDGEworkflow variablesworktree.enabledper-workflow policy (archon-assistnow runs in live checkout)mutates_checkout: falsefor concurrent runs on live checkoutagents:)~/.archon/{workflows,commands,scripts}/(collapses awkward~/.archon/.archon/workflows/)archon-skill-syncworkflow (fork-local)<repo>/.envstripped,<repo>/.archon/.envrepo-scope,~/.archon/.envhome-scope)loadArchonEnv(cwd)helper in@archon/paths/env-loaderarchon setup --scope home|projectflagFixed
archon-piv-loop.yamlhad two committed<<<<<<< HEADconflict markers from an earlier cherry-pick — cleaned uparchon-adversarial-devsedportable on macOS (nosed -i)axiosbumped to^1.15.0via override (CVE-2025-62718)opus[1m]alias instead ofclaude-opus-4-5-20250929Removed
@anthropic-ai/claude-agent-sdk/embedimport path (replaced by explicit binary-path resolution)Post-merge steps (skipped Step 10-12 of the release skill)
The fork's
homebrew/archon.rbstill referencescoleam00/ArchonURLs and the fork doesn't have its own homebrew tap (prospapledge88/homebrew-archondoesn't exist). After merging, only:v0.5.0on the fork'smainprospapledge88/ArchondevwithmainNo homebrew formula update, no tap sync, no
/test-release brew— those run oncoleam00/Archononly.Merging this PR releases 0.5.0 to
prospapledge88/Archonmain.