Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .archon/workflows/defaults/archon-workflow-builder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ nodes:
# --- script node (TypeScript via bun, or Python via uv — no AI, stdout = $<nodeId>.output) ---
# Use for deterministic data transforms the shell would mangle (JSON parsing, etc.)
script: |
const raw = String.raw`$other-node.output`;
const data = JSON.parse(raw);
// JSON is valid JS expression syntax — assign directly (String.raw breaks on backticks)
const data = $other-node.output;
console.log(JSON.stringify({ count: data.items.length }));
runtime: bun # required: 'bun' (.ts/.js) or 'uv' (.py)
# deps: [requests] # uv only
Expand Down Expand Up @@ -176,7 +176,13 @@ nodes:
3. Every node MUST have a unique kebab-case `id`
4. Use `depends_on` to define execution order
5. Use `bash` nodes for deterministic shell operations (file checks, git commands, installs)
6. Use `script` nodes for typed data transforms (TypeScript JSON parsing, Python with deps) — stdout is captured as output, stderr is forwarded as a warning. $nodeId.output is NOT shell-quoted in script bodies — parse with JSON.parse / json.loads, not shell interpolation
6. Use `script` nodes for typed data transforms (TypeScript JSON parsing, Python with deps)
— stdout is captured as output, stderr is forwarded as a warning.
$nodeId.output is NOT shell-quoted in script bodies.
- **TypeScript/bun**: assign directly — `const data = $nodeId.output;`
(JSON is valid JS expression syntax; avoid String.raw — it breaks on backticks)
- **Python/uv**: use json.loads — `import json; data = json.loads("""$nodeId.output""")`
Never interpolate into shell syntax.
7. Use `prompt` nodes for AI reasoning tasks
8. Use `approval` nodes to pause for human review at risky gates (plan→execute boundary, destructive actions)
9. Use `output_format` on prompt nodes when downstream nodes need structured data
Expand Down
9 changes: 4 additions & 5 deletions .claude/skills/archon/examples/dag-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,13 @@ nodes:
# Deterministic parsing the shell would mangle — extracts labels cleanly as JSON.
#
# NOTE: `$fetch-issue.output` is substituted *raw* into the script body (no shell
# quoting — see reference/variables.md). Wrapping it in a String.raw template
# preserves backslashes and newlines in the JSON payload without needing any
# escaping. Safe here because gh issue view --json emits clean JSON.
# quoting — see reference/variables.md). JSON is valid JS expression syntax —
# assign directly without String.raw or JSON.parse. String.raw breaks if the
# output contains backticks (e.g. markdown code spans in AI-generated content).
- id: extract-labels
script: |
const raw = String.raw`$fetch-issue.output`;
try {
const issue = JSON.parse(raw);
const issue = $fetch-issue.output;
const labels = (issue.labels ?? []).map((l) => l.name);
console.log(JSON.stringify({ labels, count: labels.length }));
} catch {
Expand Down
1 change: 1 addition & 0 deletions .claude/skills/archon/references/parameter-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Things that don't fail parsing but don't do what you'd expect:
11. **Node-level `interactive: true` on an approval node or loop, without workflow-level `interactive: true`** → on the Web UI, gate messages never reach the user. The workflow dispatches to a background worker that can't deliver chat messages.
12. **Missing env var in MCP config** → warning logged, node continues with empty string substitution.
13. **`retry` on a loop node** → this one is a **hard parse error** (not silent). Use the loop's own `max_iterations` and `until_bash` for finish-line detection.
14. **`String.raw\`$nodeId.output\`` in a `script:` body** → silently corrupts when the substituted value contains a backtick (e.g. markdown code spans in AI output or `output_format` payloads). The template literal terminates early, producing a cryptic `Expected ";"` parse error. Use direct assignment instead: `const data = $nodeId.output;` — JSON is valid JS expression syntax and needs no wrapper.

The pattern across these: if you set an AI feature on a non-AI node, it's silently ignored. Watch loader logs for `_ignored` warnings when debugging.

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/archon/references/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ All variables are available in all workflows. The only exception is `$nodeId.out
- **Command files** (`.archon/commands/*.md`) — all variables except `$nodeId.output`
- **Inline `prompt:` fields** — in DAG prompt nodes and loop node prompts
- **`bash:` scripts in DAG nodes** — `$nodeId.output` references are automatically shell-quoted (single-quoted with `'` escaped)
- **`script:` bodies in DAG nodes** — same substitution as bash, but `$nodeId.output` values are **NOT** shell-quoted. Parse with `JSON.parse` / `json.loads` rather than interpolating into shell syntax
- **`script:` bodies in DAG nodes** — same substitution as bash, but `$nodeId.output` values are **NOT** shell-quoted. For TypeScript/bun scripts, assign directly (`const data = $nodeId.output;`) — JSON is valid JS expression syntax. **Avoid `String.raw\`$nodeId.output\``** — it silently breaks when the output contains a backtick (common in AI-generated markdown and `output_format` payloads).

## Substitution Order

Expand Down
3 changes: 2 additions & 1 deletion .claude/skills/archon/references/workflow-dag.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ Runs TypeScript/JavaScript (via `bun`) or Python (via `uv`) without AI. Same std
- **stdout** captured as `$nodeId.output` (trailing newline trimmed)
- **stderr** forwarded as warning, does NOT fail the node. Non-zero exit DOES fail it.
- **`bun --no-env-file`** prevents target repo `.env` from leaking into the subprocess
- `$nodeId.output` substitutions are **NOT shell-quoted** in script bodies — parse with `JSON.parse` / `json.loads`, don't interpolate into shell syntax
- `$nodeId.output` substitutions are **NOT shell-quoted** in script bodies — assign directly (`const data = $nodeId.output;`) or parse with `JSON.parse` / `json.loads`; don't interpolate into shell syntax
- **CAUTION — `String.raw\`$nodeId.output\`` is fragile**: if the substituted value contains a backtick (common in AI-generated markdown, `output_format` payloads, or any content with code spans), the template literal terminates early and produces a cryptic `Expected ";"` parse error. Use direct assignment instead — JSON is valid JS expression syntax and needs no wrapper.
- AI-specific fields (`model`, `provider`, `hooks`, `mcp`, `skills`, `output_format`, `allowed_tools`, `denied_tools`, `agents`, `effort`, `thinking`, `maxBudgetUsd`, `systemPrompt`, `fallbackModel`, `betas`, `sandbox`) emit a loader warning and are ignored

### Loop Node
Expand Down
19 changes: 19 additions & 0 deletions packages/docs-web/src/content/docs/guides/script-nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,25 @@ shell quoting** — unlike `bash:` nodes, where `$nodeId.output` values are
auto-quoted. Treat substituted values as untrusted input and parse them with
language features, not by interpolating into shell syntax.

:::caution[Avoid String.raw with `$nodeId.output`]
The pattern `` String.raw`$nodeId.output` `` looks safe but fails silently when
the substituted value contains a backtick — common in AI-generated markdown,
`output_format` payloads, or any output with inline code spans. The backtick
terminates the template literal early, producing a cryptic `Expected ";"` parse
error at runtime.

**Use direct assignment instead.** JSON is a strict subset of JavaScript
expression syntax, so the substituted value is always a valid JS literal:

```typescript
// Safe — works for any valid JSON, including content with backticks
const data = $fetch-issue.output;

// Fragile — breaks if output contains a backtick
const data = JSON.parse(String.raw`$fetch-issue.output`); // DON'T
```
:::

For **named scripts**, variables are not passed automatically. Read them from
the environment (`process.env.USER_MESSAGE`, `os.environ['USER_MESSAGE']`)
or accept them via stdin. For **inline scripts**, substituted variables are
Expand Down

Large diffs are not rendered by default.

Loading