# Unattended runs: the artifact contract A gstack skill runs *unattended* when a parent agent starts it with `GSTACK_SESSION_KIND=unattended` and reads its artifacts instead of its prose. This page is the contract between that parent and the run: what the session kind changes, which files a run writes, how the parent decides whether the run finished, and how it reads the result without reading any markdown. The session kinds are: `interactive` (a human answers prompts), `headless` (`GSTACK_HEADLESS`, `CI`, the eval harness; a question BLOCKs), `spawned` (an orchestrator subagent; the recommended option is auto-chosen) and `unattended` (this page). `unattended` is honored only as the explicit `GSTACK_SESSION_KIND=unattended` override, which outranks `GSTACK_HEADLESS` so a parent can drive a run inside CI. Nothing in the test or eval harness sets it, so `headless` keeps its BLOCK semantics. ## What the session kind changes `gstack-skill-start` prints `SESSION_KIND: unattended` and `UNATTENDED_SESSION: true`, then applies these rules for the whole skill: - **No prompts.** The update check, upgrade offer, telemetry and sync consent, onboarding tips and routing blocks are suppressed exactly as they are for `spawned`. An unanswered consent is `off`. - **No publishing or sync, enforced at the writers.** Usage analytics are not written (`TELEMETRY_WRITE: skipped (unattended session)`), the artifacts repo is neither pulled nor pushed (`ARTIFACTS_SYNC: skipped (unattended session; mode= queue= retained)`), the sync spool is not fed (`gstack-brain-enqueue` is a no-op, and `gstack-review-log` says `review-log: sync skipped (unattended session)` on stderr), and `gstack-skill-end` drains nothing. Inherited enabled settings and pending queue work are left for the next human session. The upgrade offer becomes one line at skill end, read from the local cache: `UPGRADE_AVAILABLE: ; run /gstack-upgrade`. - **Questions decide themselves, gates do not.** A question with a `(recommended)` option takes it and appends the choice to `decisions.jsonl` with `kind: "auto"`. A question with no recommendation, any consent, any destructive or irreversible option, and the final approval gate are appended with `kind: "approval"` and `status: "pending"`. The skill ends at its next gate with the pending list rendered; it never approves and never publishes. - **State root durability is reported.** `STATE_ROOT: durable=yes|no` (`no` when the host sets `GSTACK_EPHEMERAL=1`). On an ephemeral root the run says `learnings: skipped (state root is ephemeral; set GSTACK_STATE_ROOT)` and `timing: analytics skipped (...)` instead of writing to a disk that vanishes. Point `GSTACK_STATE_ROOT` at a durable directory when you want cross-run history (see [state-root.md](state-root.md)). - **The autoplan publication guard is named, not faked.** On hosts that do not execute Claude Code hooks, and in every unattended session, `gstack-skill-start` prints `autoplan guard: not enforced by this host; publication order is unverified (GUARD_NOT_INSTALLED)` once. `gstack-artifact validate` requires that line in an unattended run's `review-record.md`. Claude Code's deny-on-absent behavior is unchanged. An unknown `GSTACK_SESSION_KIND` value coerces to `interactive` and says so: `SESSION_KIND: interactive (unknown kind '')`. ## The terminal line Every workflow skill ends an unattended run with one line the parent greps: ``` GSTACK_RESULT: skill= status= run= ``` `status` is one of `complete`, `gate_pending`, `incomplete`, `interrupted` or `refused`; `run` is the directory that holds the artifacts below. `/autoplan` always ends `gate_pending` when it reaches the final gate, because the gate is written and never approved. Pure queries (`gstack-artifact schema`, `gstack-gate render`) print no result line. Every workflow command shares one exit table: `0` ok, `1` fail, `2` usage, `3` refused or needs a flag. Error lines end with a result code in parentheses, for example `findings.jsonl:12: ... (ARTIFACT_SCHEMA)`; each code has an anchor in [troubleshooting.md](troubleshooting.md#unattended-runs-and-artifacts) with a `fix:` clause. Grep the code, not the prose. ## The run directory | File | Schema | Contents | |------|--------|----------| | `run.json` | `run` v1 | The manifest: `run` id, `status`, `required_phases`, each phase's `native` and `outside` outcome (`status`, `model`, `provider`), input `snapshot` hashes, `counts`, every artifact's `path` (relative to the run directory) and `sha256`, `session_kind`, `gate_rev`, `deviations`, `consumed_by`. | | `decisions.jsonl` | `decisions` v1 | One row per decision: `id` (`-