TL;DR
executeCommandStep does not call writeOutputArtifacts. So command-type steps never register their declared output_artifacts in execution.ArtifactPaths. Any downstream step using memory.inject_artifacts: { step: <command-step>, artifact: <name> } falls through to the stdout-fallback path (executor.go:4566), and since most command steps redirect their work to a file (not stdout), the injection delivers a 0-byte file under .agents/artifacts/<as-name>. The persona then dutifully reports "no input" and the pipeline ships a vacuous deliverable.
Concrete trigger
ops-pr-respond on PR #1472 just now (run ops-pr-respond-20260428-182026-1f67):
| Stage |
What happened |
filter-scope (command) |
Wrote 12046-byte scoped-findings.json (15 real security/quality findings) |
triage (planner, inject_artifacts: scoped_findings) |
Got 0-byte .agents/artifacts/scoped_findings |
| Triage prompt Step 0 short-circuit fired: "empty input → write empty result" |
|
comment-back posted #1472 (comment): "No findings." |
|
Pipeline status: green. Deliverable: wrong — the PR has 15 actionable findings the auditors found, none of which reached the contributor.
This is exactly the failure mode "green ≠ correct" — don't monitor only the green lights.
File-system evidence
```
$ stat -c '%s %n' .agents/workspaces/ops-pr-respond-20260428-182026-1f67/triage/.agents/artifacts/*
4096 .../triage/.agents/artifacts/fetch-pr ← persona step, fine
532 .../triage/.agents/artifacts/pr_context ← persona step, fine
0 .../triage/.agents/artifacts/scoped_findings ← command step, broken
0 .../triage/.agents/artifacts/scope_stats ← command step, broken
```
Root cause (executor.go)
- Persona/adapter path: line 3572-3583 calls `writeOutputArtifacts` after the adapter returns. This populates `execution.ArtifactPaths[step.ID+":"+art.Name]`.
- Command path (`executeCommandStep`, lines 1424-1614, dispatched from 1786): writes the script's output files to disk, but never populates `ArtifactPaths`. Returns directly to `validateStepContracts` (which reads files from disk and so doesn't notice).
- Downstream `injectArtifacts` (line 4555) looks up by `step.ID+":"+ref.Artifact`. Miss → falls through to `execution.Results[ref.Step]["stdout"]` (line 4567). Command steps almost never echo their artifact bytes to stdout, so the injection writes empty data.
Scope of impact
Any pipeline that uses a `type: command` step to produce JSON for a downstream persona via `inject_artifacts`. At least:
- `ops-pr-respond` — `filter-scope` → `triage`
- Likely others — needs grep audit
The newer auto-injection path (#1452, `injectDependencyArtifacts`) does register paths via `ArtifactPaths` for command-step deps when it walks the upstream output_artifacts list. But `memory.inject_artifacts` is the legacy explicit path and bypasses that wiring.
Fix sketch
In `executeCommandStep` (or just after it returns at line 1788), call `writeOutputArtifacts(execution, step, workspacePath, nil)`. The function already handles the "persona/script wrote the file, don't overwrite" case correctly (line 4715: `os.Stat(artPath) == nil` → register existing path).
This is the minimum fix. A better fix is to unify the artifact-registration pass for both paths so the lookup logic is identical regardless of step type.
Test for the fix
Live re-run of `ops-pr-respond 1472`: expect non-empty `scoped_findings` injection, non-empty `triaged-findings.actionable`, substantive PR comment listing actual findings.
Severity
High. Composition primitives, audit-* aggregation, and any "command preprocessor → LLM consumer" pattern silently degrade to no-op. The pipeline shipping pretends success was 100% the spec's fault until you read the deliverable.
TL;DR
executeCommandStepdoes not callwriteOutputArtifacts. Socommand-type steps never register their declaredoutput_artifactsinexecution.ArtifactPaths. Any downstream step usingmemory.inject_artifacts: { step: <command-step>, artifact: <name> }falls through to the stdout-fallback path (executor.go:4566), and since most command steps redirect their work to a file (not stdout), the injection delivers a 0-byte file under.agents/artifacts/<as-name>. The persona then dutifully reports "no input" and the pipeline ships a vacuous deliverable.Concrete trigger
ops-pr-respondon PR #1472 just now (runops-pr-respond-20260428-182026-1f67):filter-scope(command)scoped-findings.json(15 real security/quality findings)triage(planner,inject_artifacts: scoped_findings).agents/artifacts/scoped_findingscomment-backposted #1472 (comment): "No findings."Pipeline status: green. Deliverable: wrong — the PR has 15 actionable findings the auditors found, none of which reached the contributor.
This is exactly the failure mode "green ≠ correct" — don't monitor only the green lights.
File-system evidence
```
$ stat -c '%s %n' .agents/workspaces/ops-pr-respond-20260428-182026-1f67/triage/.agents/artifacts/*
4096 .../triage/.agents/artifacts/fetch-pr ← persona step, fine
532 .../triage/.agents/artifacts/pr_context ← persona step, fine
0 .../triage/.agents/artifacts/scoped_findings ← command step, broken
0 .../triage/.agents/artifacts/scope_stats ← command step, broken
```
Root cause (executor.go)
Scope of impact
Any pipeline that uses a `type: command` step to produce JSON for a downstream persona via `inject_artifacts`. At least:
The newer auto-injection path (#1452, `injectDependencyArtifacts`) does register paths via `ArtifactPaths` for command-step deps when it walks the upstream output_artifacts list. But `memory.inject_artifacts` is the legacy explicit path and bypasses that wiring.
Fix sketch
In `executeCommandStep` (or just after it returns at line 1788), call `writeOutputArtifacts(execution, step, workspacePath, nil)`. The function already handles the "persona/script wrote the file, don't overwrite" case correctly (line 4715: `os.Stat(artPath) == nil` → register existing path).
This is the minimum fix. A better fix is to unify the artifact-registration pass for both paths so the lookup logic is identical regardless of step type.
Test for the fix
Live re-run of `ops-pr-respond 1472`: expect non-empty `scoped_findings` injection, non-empty `triaged-findings.actionable`, substantive PR comment listing actual findings.
Severity
High. Composition primitives, audit-* aggregation, and any "command preprocessor → LLM consumer" pattern silently degrade to no-op. The pipeline shipping pretends success was 100% the spec's fault until you read the deliverable.