Cloud runs
This page is for you if you want a planned change to run in a vendor's cloud. On Claude Code on the web and Cursor's cloud agents it can run through a night with nobody there. Copilot's cloud agent runs it as a chain of sessions, each stopped at 59 minutes, and a person continues each one, so it is no night with nobody there. For each cloud it gives the setup, the branch protection the run needs before it starts, the secrets, how to start a run under a run contract, and the morning walk. Then it covers Claude Code's GitHub Action, which is a short surface rather than an overnight one, and the run contract itself.
The run is an ordinary touchpoint, usually /st-work, in unattended mode.
Working with stamity covers the touchpoints, and the
st-unattended skill is the text the run follows. The
vendor facts below are rows of the capability matrix's cloud table, each
with the vendor page it was read from and the date; this page names no vendor address.
What asks, and what refuses
A run is unattended only by declaration: --unattended on the touchpoint, or STAMITY_UNATTENDED
set to any value but empty, false or 0. A cloud's own marker means "cloud", never "unattended".
- An unattended run asks nothing. Each question takes its declared default, written into the
record as
Default applied:, and a question with no default stops the run. It pushes only through the run verbs,stamity run checkpointandstamity run end: the shell guard refuses a rawgit push,gh pr createorgh pr edit, and every person-only act, on every client. - Attended, Claude Code asks for one click on a person-only act that can be undone, such as a
merge, a push of a tag or of the default branch, a ready-for-review mark, a release, a publish, or
a secret or protection write, and the agent runs it on yes. It refuses instead where a sub-agent
runs the act, and in Claude Code's
autopermission mode. - Attended, Copilot CLI asks too, for your own agent alone. The guard asks only for a call that carries no helper agent's fields and whose session id Copilot's session start recorded; a helper's call is refused. Copilot CLI 1.0.98 showed its hook permission dialog and waited for the person (measured 2026-10-19). Its experimental assisted mode can hand the ask to an LLM judge, which may approve it without you, and a hook cannot see that mode, so leave it off if the click must be yours.
- Cursor and Codex refuse that act and hand you the exact command. Cursor ran a command despite
an ask under
--force, and its hooks page says the ask is not enforced; Codex's hooks page says an ask is not honoured. - Every deletion and every force push is refused on every client, attended or not, with a click or without one.
SECURITY.md has the full person-only table and what the guard cannot read.
What every cloud needs
- The setup on the default branch. Commit what
initandsyncwrite, as getting started says, so a fresh clone in the cloud carries the hooks. - The CLI. A run calls
npx --no stamity <verb>, an installed copy, and falls back to the pinned version the setup was generated with. With neither, it refuses:Refused: the stamity CLI is not reachable. - Protection the run's account cannot get past. The run's first act is
stamity gate preflight --unattended, and itsdirect-pushcheck reads, over GitHub's REST API, whether the account the cloud'sghacts as can push to the default branch. It refuses unless the account cannot. Those reads go throughgh, so the cloud needsghinstalled and signed in, and they prove something only when every URL git pushes origin to goes out as that account: over https with gh's credential helper the only helper for origin and noAuthorizationextra header, or through Claude Code's cloud proxy. Otherwise the check does not pass, and its fix names the git configuration to set. For an account without admin, an active ruleset or classic protection that requires a pull request, with no direct-push bypass for that account, counts; a classic pull-request bypass that lists any team or app does not, since nothing proves the account is not in it. For an account that administers the repository, only an active organization ruleset counts, only one whose branch target is~ALLor the default branch by its own name, with no exclude that could match it, and whose repository target is this repository's id or every repository by name (not~DEFAULT_BRANCH, a name pattern or a property), and only while the account is a plain member of the organization, never an owner. A refusal's evidence names the rulesets and protection it read and why none counted. An account with no push right at all is refused too, since the run could not push its own branch. - A plan the ready check passes.
stamity plan checkmust exit 0 on the plan and the contract before the run frames anything.
Claude Code on the web
Set it up
Start one repository per session: a session holding several repositories loads none of their
hooks, and the pre-flight's hooks-loaded check refuses it. Set STAMITY_UNATTENDED=1 in the
cloud environment. The setup ships a readiness hook for this cloud. Where CLAUDE_CODE_REMOTE is
true it marks the session's hooks loaded and runs npm ci when node_modules is missing and a
lockfile is there. Locally it does nothing. The GitHub proxy refuses GraphQL, so gh pr and
gh issue fail there; the run verbs open and update the draft pull request over REST. A machine
that was reclaimed comes back fresh, and the review gate rebuilds its round counter from the run
record on the branch.
The environment needs a Node of at least the CLI's engines floor, 22.22.2: on 2026-10-19 the
cloud's own Node was 22.22.0, and the pre-flight's node-engine check refused it. Also run
gh auth setup-git in the cloud environment before the run starts, so that gh is origin's
credential helper: by default git pushed origin over https with no credential helper, and
direct-push refused.
Protect the default branch
The proxy refuses a tag push and a branch deletion, but not a branch push, the default branch's
included. So branch protection or a ruleset is the only thing that keeps a run off the default
branch, and the pre-flight's direct-push check refuses to start a run without it.
Secrets
The run needs no secret of its own: it acts as the cloud's GitHub connection. An MCP server's
credential is read as ${VAR} from the session's environment. .env.mcp is git-ignored, so set the
variable in the cloud environment, never in a committed file. The pre-flight warns token-env,
naming each credential-shaped variable it sees, by name only.
Start a contract run
In a new cloud session on the repository, send
/st-work --unattended docs/plans/<file>.md (run contract: <path>). The run runs the pre-flight,
then stamity plan check docs/plans/<file>.md --contract <path> --json, and any exit but 0 is a
refusal.
The morning walk
The draft pull request's body is the morning list. A question Claude asks holds the session idle
until someone answers, and an unattended run asks none. If the session is still open, the run ended
on run end --final-round and asks one final round there. The variable is still set, so each held
act comes to you as the exact command. Or walk it locally: /st-work --resume <run-id> on the run's
branch, where in Claude Code each held act that can be undone asks for your click.
Cursor's cloud agents
Set it up
Cursor's cloud agents run the repository's .cursor/hooks.json command hooks, except
sessionStart, sessionEnd and the MCP hooks. So for Cursor the setup adds three things. The
root AGENTS.md ends on a "Cursor Cloud specific instructions" block: what /st-<id> means,
node --version first, and the pull request kept a draft. A postToolUse hook prints the session
index once per conversation. A stop hook keeps an unattended run going while its record leaves
units. stamity check's cloud-install row prints the install lines, node --version and then
the CLI's global install at the version that generated the setup. Put them in the install step of
the environment you saved in Cursor, with STAMITY_UNATTENDED=1, and give that environment a gh
that is signed in and is origin's only credential helper, which the pre-flight's direct-push
check needs. The saved environment's origin URL must not carry credentials: in that install step,
set origin's URL, and its push URL where one is set, to the repository's plain https URL, then run
gh auth setup-git. The first check showed why: on 2026-10-19 Cursor's origin push URL carried its
own credentials, which cannot be tied to gh's identity, and direct-push refused. init and
sync write no .cursor/environment.json, because a repository file replaces every environment
saved there.
Protect the default branch
The push Cursor's agent makes is the platform's, not a shell call, so no shell guard sees it. The
remote's protection is what holds, and the pre-flight's direct-push check refuses to start a run
without it.
Secrets
An MCP server's credential is read as ${env:VAR}. Set it in the saved environment's secrets, never
in a committed file. The pre-flight's token-env warning names each credential-shaped variable,
by name only.
Start a contract run
Start a new cloud agent on the repository with
/st-work --unattended docs/plans/<file>.md (run contract: <path>). The run runs the pre-flight,
then stamity plan check docs/plans/<file>.md --contract <path> --json, and any exit but 0 is a
refusal. The session line names a Cursor cloud agent, and in a managed run the agent asks nothing
and takes each declared default.
Cursor's page does not say whether the pull request its agent opens is a draft, so the run opens its
own draft through the run verbs and never marks it ready.
The morning walk
Read the morning list in the draft pull request's body, and answer the way its
## How to answer section names. Cursor's guard refuses every person-only act rather than asking,
so each held act comes to you as the exact command, the morning walk included. Locally,
/st-work --resume <run-id> on the run's branch walks the same list.
Copilot's cloud agent
Set it up
The cloud agent uses a hooks file only when it is on the default branch, so .github/hooks/
must be on the default branch before the first run. copilot-setup-steps.yml installs the CLI;
the agent's environment also needs a gh that is signed in and is origin's only credential
helper, which the pre-flight's direct-push check needs.
A prompt file is not run by name on this agent, so the setup writes a thin entry agent,
.github/agents/stamity-run.agent.md, that points at /st-work's prompt file and the plan the
issue names. While that agent is not yet on the default branch, the run writes a
## Prerequisites row naming it.
Protect the default branch
The agent pushes to one branch and opens a draft pull request, and cannot mark it ready, approve it
or merge it. The run still starts only past the pre-flight, whose direct-push check reads the
account the agent's gh acts as and refuses without protection. Workflows on its pull request wait
for a write-access person's approval unless the repository lets them run, so the proof block then
reads CI not run: awaiting approval.
Secrets
An MCP server's credential is read as $COPILOT_MCP_<VAR>. Set it in the agent's environment,
never in a committed file. The pre-flight's token-env warning names each credential-shaped
variable, by name only.
Start a contract run
Assign the agent an issue, with the stamity-run agent, whose text is
/st-work --unattended docs/plans/<file>.md (run contract: <path>). The run runs the pre-flight,
then stamity plan check docs/plans/<file>.md --contract <path> --json, and any exit but 0 is a
refusal. Each session stops at 59
minutes, so a long plan runs as a chain of sessions, each ending in a pushed checkpoint. A
write-access person's @copilot continue on the pull request starts the next one, which resumes
from the committed record. Copilot's unattended facts are documented and not run: no person check
has run them, for want of a Copilot cloud-agent account.
The morning walk
Answer in one comment on the pull request, @copilot answers: Q1→2, Q3→1. Only a write or
admin author's comment answers; anyone else's is listed on the next morning list, not applied.
Copilot's documentation says its cloud agent denies a hook's ask, since nobody is there to click it,
so an act the guard asks for does not run there. The denied ask tells the agent only that the act is
yours to approve, not to give you the command, so whether its reply names the command is up to the
agent: ask it for the command and run it yourself. Where the guard refuses instead (a helper agent's
call, or a session Copilot's session start did not record), the refusal tells the agent to give you
the exact command.
Codex Cloud
Its page documents no project hooks, so assume none run there. No guard writes the pre-flight's
canary, hooks-loaded fails, and an unattended start is refused. Run Codex locally.
Claude Code's GitHub Action
The Action runs Claude Code as a GitHub Actions job, and a job on a GitHub-hosted runner is stopped and failed at 6 hours of execution time (documented). It is a surface for a short task, one unit rather than a plan, and never an overnight surface. Its page names no question tool among the tools a run is given by default, and nobody sits in the job to answer one. That it has none rests on the page's silence (not documented), so a run there takes its declared defaults.
On a pull request, the Action restores .claude/, CLAUDE.md and the other Claude configuration
paths from the base branch before Claude starts (documented). Stamity's own entries in the restored
.claude/settings.json run copies of its hook scripts under .claude/hooks/stamity/, beside the
copy of the agent tool policy document the guard reads, so the restore covers them too: on that run
Stamity's own hooks run the base branch's scripts, and a pull request cannot change what the guard
runs on its own Action run. The copies under .stamity/generated/hooks/claude/ are still written,
for the session start the charter has a session run by hand where no hook ran; no hook entry runs
them. That holds only once the base branch carries a sync that writes those copies. Before it,
the base branch's entries run the scripts under .stamity/generated/hooks/claude/, and those come
from the pull request.
What the restore does not cover still comes from the pull request. The base branch's CLAUDE.md
imports AGENTS.md, which the restore does not cover, so the agent on that run reads that pull
request's AGENTS.md. What the hook scripts read from the rest of the checkout, the run records,
the learnings and the resumable handoffs under .stamity/, are that pull request's too. And hooks of your own and hooks a
pack supplies are wired into the same settings but run scripts outside .claude/, at their own
paths, so those scripts come from the pull request, and the Action runs them with its token. Run the
Action on a pull request only when you would let its author change the charter. Before you run it on
pull requests from outside, keep such scripts under .claude/, or drop them.
The restore has one limit the Action names as its own, in its source rather than its documentation
(read 2026-10-19). A pull request that changes .claude/ sees that change reverted on the Action's
run, so its new hooks and settings do not run there. And when the agent on that run commits with
git add -A, the revert goes into that commit, onto the pull request's branch.
The run contract
A run contract is a committed YAML file that states, by reference, what one unattended run is for. No trigger reads it yet: you start the run and name the file. The engine reads it strictly. An unknown key at any level is refused, and so is every defect, all in one pass.
run-contract: 1
plan: docs/plans/042-search-index.md
item: "#412"
base: 02e040eb
acceptance: |
The search index rebuilds in under a minute, and its tests pass.
scope:
in: [src/search/, test/search/]
out: [src/auth/]
prerequisites:
- { name: the staging index, state: done }
- { name: the search service credential, state: owned, owner: "@ops" }
budget: { stop-by: "2026-10-20T06:00:00Z", max-units: 6 }
answerer: "@maintainer"
preview: stop
move: off
run-contractis the version,1. Any other is refused, naming version 1.planis a repository-relative plan path anditema board item or issue. A contract names one or both.baseis the commit the run starts from, 7 to 64 hexadecimal characters.acceptanceis kept verbatim.scopelists the paths the run worksinand keepsoutof.prerequisitesare eachdone, orownedby a namedowner.budgetholdsstop-by, a day or a time with its offset, andmax-units.answereris who answers the morning list.preview: stopandmove: offare fixed, and an absent key reads the same. A run under a contract stops at a board write's preview and moves no card: each write it would make is a morning-list row.
The ready check is the start check: stamity plan check docs/plans/<file>.md --contract <path>.
It reports a contract defect as contract, a prerequisite neither done nor owned as
prerequisite-open, and a contract whose plan names none of the plans checked as
contract-mismatch. Any exit but 0 refuses the run. The engine checks the file's shape and its
plan. The budget, the scope and the acceptance are the run's instructions, which it follows as
text, and the draft pull request's diff is where you check them.
What a run leaves
- A branch whose commits the run verbs made, each subject
chore(run): checkpoint <run-id>:stamity/<run-id>where the run started on the default branch or a detachedHEAD, and otherwise the branch the session was given, a platform's own (claude/…,cursor/…,copilot/…) included. The pull request's head names it. - The record,
.stamity/runs/<run-id>/record.md, opening onMode: unattended,Branch:andBase:. - One draft pull request whose body is
.stamity/runs/<run-id>/morning.md:## For you to check,## Decisions,## Prerequisites,## Person's calls held,## Not done:and## How to answer. Where the repository takes no drafts, it is a normal pull request titled[DRAFT] <title>. Where a pull request this run did not open is already open from the branch into the base, a person's or the platform's, the run posts the body on it as one comment. - A change to a held path, such as a CI workflow, the hook configuration or the charter, never
reaches the pushed tree. It waits in
.stamity/runs/<run-id>/held/person-only.patchas a row of## Person's calls held.
When it stops
- Refused. The pre-flight or the ready check refused, and the morning list opens on
## Refused, the pull request titledRefused:. A refusal before the run's branch exists is one reply on the start surface instead. - Blocked. A question with no declared default stops at
BLOCKED_AMBIGUITY. A usage limit stops atBLOCKED_DEPENDENCYnaming the reset: the record stays in progress, and the next session on the branch continues it. - Not pushed. A run verb that exits 1 keeps its
Not done:lines for the morning list, such asNot done: draft PR not opened, and is never retried with a raw push or a force. - No CLI. A run whose CLI stops answering closes its record and ends on
Not done: run end not run — CLI unavailable.
How this page stays current
Each release re-reads the vendor pages the capability matrix's cloud table cites, and a page that
changed since its read date re-owes that cloud's person check at the next release. The container
leg, CI's cloud-container job, runs on every full CI run. The hook-fire check runs nightly where
its secrets exist; no release-prep job runs it. At release prep the release checklist runs
scripts/qa/hook-runs.mjs --ask by hand, with a person's one-minute check of each ask-class
client's interactive ask beside it, in an interactive Claude Code session and an interactive Copilot
CLI session. The person checks, one unattended run per cloud in a scratch
repository, run with scripts/qa/cloud-check.mjs, and the capability matrix reads "not run" for a
fact no person check has run.