Artifacts
Artifacts let a task produce files — reports, screenshots, build outputs — that humans can view or download from the UI, CLI, or MCP after the job completes.
Producing artifacts
Section titled “Producing artifacts”Write files to /artifacts/ (or the $ARTIFACTS_DIR env var for portability):
name: nightly-reportsteps: - name: build action: type: script runner: local language: shell script: | ./tools/report --out "$ARTIFACTS_DIR/report.html" cp build/dist.zip "$ARTIFACTS_DIR/"When the step succeeds, the worker uploads every file in /artifacts/ (recursively). Failed or cancelled steps discard their /artifacts/ directory.
What gets uploaded
Section titled “What gets uploaded”- Regular files (recursive —
reports/q1.htmlkeeps the slash in its name). - Dotfiles (e.g.
.config). Don’t put.envhere. - Empty files.
- Symbolic links are skipped + warned.
- Filenames with
.., null bytes, or control characters are rejected + warned.
Limits
Section titled “Limits”| Default | Configurable | |
|---|---|---|
| Per-file | 100 MiB | artifact_storage.max_file_bytes |
| Per-job | 1 GiB | artifact_storage.max_job_bytes |
Exceeding either fails the step loudly.
Runner support
Section titled “Runner support”| Runner | Supported |
|---|---|
| Shell (local) | Yes |
Docker (script:docker) |
Yes |
Docker (type:docker) |
Yes |
| Kubernetes (any) | No (deferred — same limitation as state /state-out) |
| Agent / Approval / Task actions | n/a |
On unsupported runners, $ARTIFACTS_DIR is unset; scripts that try > "$ARTIFACTS_DIR/foo" will fail loudly with “ambiguous redirect”.
Viewing & downloading
Section titled “Viewing & downloading”UI: a new Artifacts section appears near the top of the Job Detail page. Safe MIME types (PNG, JPEG, GIF, WebP, plain text) open inline in a new tab. Everything else — including HTML and SVG (for XSS hardening) — downloads.
CLI:
stroem-api artifacts list <job-id>stroem-api artifacts download <job-id> report.html -o ./report.htmlHooks: hook.artifacts is a list of {name, content_type, size_bytes, url, step_name, created_at}. Example Slack notification:
on_success: - action: slack-notify input: text: | Job done. Outputs: {% for a in hook.artifacts %}- <{{ a.url }}|{{ a.name }}> ({{ a.size_bytes }} B)\n{% endfor %}MCP: list_artifacts(job_id) and get_artifact(job_id, name). get_artifact returns text/JSON/YAML/XML as inline text, image/* as image blocks the agent can view, and other binary (PDF, archives, …) as a base64 blob resource — all capped at 1 MiB. Larger artifacts must be fetched via the HTTP artifact URL from list_artifacts rather than embedded in the agent’s context.
Retention
Section titled “Retention”Artifacts live as long as the job row does. Once a job is deleted (via the existing retention.job_days sweep), its blobs and rows go with it. There is no separate artifact TTL today.
Security notes
Section titled “Security notes”Artifacts produced by a workflow are served with the stored Content-Type, but anything that could execute in the browser (HTML, SVG, XML) is force-downloaded rather than rendered. X-Content-Type-Options: nosniff blocks browsers from second-guessing.
View permission on the task is sufficient to list and download artifacts — the same permission required to view logs. Don’t put secrets in /artifacts/.