Git push from a session
How the edits of a remote session leave it as a git commit: ciresk dev push turns the session's workspace into one commit on the session's own branch and, with --pr, opens a pull request for it. A local session never needs this: its edits are your worktree's, and you commit and push them with your own git.
Pushing from a session needs the installation's forge to be turned on. It is not on in
eu1yet, so in Ciresk's cloud everyciresk dev pushis refused today withpush_unavailable. Everything on this page that does not push (checkpoints, stopping and continuing a remote session, pushing from your own checkout) works without it.
Which sessions push
| Session | Started with | Where its edits are | How they reach git |
|---|---|---|---|
| Local | ciresk dev up | your worktree, synced into the session | git commit and git push in your worktree, as always |
| Remote | ciresk dev up --remote, ciresk dev start | the session's workspace, no local copy | ciresk dev push |
A push from a local session is refused with not_cloud_writer; the answer tells you to push from your own checkout.
Push
Run it in the worktree whose remote session you want to push:
ciresk dev push
ciresk dev push --pr
ciresk dev push --pr --base main --title "fix the invoice rounding"| Flag | Default | What it does |
|---|---|---|
--base <branch> | the branch checked out in this worktree | the branch the pull request targets |
--pr | off | also open the session's pull request into --base, or find the one already open |
--title <text> | ciresk: <session label> | the commit message's first line and the pull request's title: one line, 1 to 256 bytes |
--idempotency-key <key> | a new key | a retried call with the same key acts once |
--pr needs a base: with no branch checked out and no --base, it is refused before anything is sent.
What a push does, in one step on the host that runs the session:
When nothing changed since the base, the first push still creates the session's branch, at the base commit, so a pull request asked for with it has a branch to come from.
The commit:
- Branch: always
refs/heads/ciresk/<session>. No flag names another branch, a tag, a remote or a force. A session continued from a stopped or ended one keeps writing the first session's branch. - Parent: always the session's base. If the branch moved to a commit the session did not push, the push is refused with
non_fast_forwardand the branch is left as it is. - Author: the session's owner, with the name and email your organization's directory gives them; without them, the owner's id at an address that receives no mail. Committer:
Ciresk <[email protected]>. - Message: the title, then these trailers:
| Trailer | Value |
|---|---|
Ciresk-Session | the session id, devs_... |
Ciresk-Pushed-By | who asked for the push, user:<id>: the person, or the person an agent acts for |
Ciresk-Agent | the agent that asked, agent:<id>; only when an agent pushed |
fix the invoice rounding
Ciresk-Session: devs_...
Ciresk-Pushed-By: user:01H...
Ciresk-Agent: agent:01H...- Pull request body: the session id and the checkpoint's tree root.
Output:
pushed 3f9c2a1 to refs/heads/ciresk/devs_...
files: 4
pull request: https://github.com/acme/app/pull/812 (opened)A push with nothing new since the last one prints already pushed <sha>. A pull request that was already open prints (existing). --json returns the result as data:
| Field | What it is |
|---|---|
reference | refs/heads/ciresk/<session> |
old | the branch's commit before the push, null when the push created it; equal to new when nothing changed |
new | the commit the branch now points at |
tree_root | the checkpoint the commit holds |
files_changed | how many files the commit changed |
change_request | with --pr: {id, url, created}, created is false when it was already open |
A push may take up to about two minutes. If it is still running when the command stops waiting, the command prints still pushing, exits with code 7 and names the call to make: run ciresk dev push --idempotency-key <key> with the same key to get the result, without pushing twice. A push that is cut off because the session stopped or was killed is refused with session_ended, and nothing that had not reached the forge was pushed.
ciresk dev status shows the session's last push as pushed: <short sha> <pull request url>.
Workflows
A session never pushes a commit that would start a GitHub Actions workflow on its branch, because such a workflow would run the session's code with your repository's secrets. The push is refused with push_unsafe_workflows, and nothing is pushed, when:
- the change touches anything under
.github/workflows/; or - a workflow in the commit (any
.ymlor.yamlfile directly under.github/workflows/, usually your base branch's own) runs on a push tociresk/<session>or on the creation of a branch. That is a workflow with acreatetrigger, or with apushtrigger that has no branch filter, apathsfilter alone, or abranchesorbranches-ignorelist that does not leaveciresk/<session>out. A workflow file that does not parse counts as running, and so does a branch pattern with?,+,[or\.
To fix it, change the workflow on your base branch, from your own checkout, then push again:
on:
push:
branches-ignore: ['ciresk/**']
pull_request:Give every push trigger branches-ignore: ['ciresk/**'] (or a branches list that does not match ciresk/..., such as [main]), and drop any create trigger. A change to .github/workflows/ itself is always made from your own checkout.
A change whose path has a .git component, in any case or in a spelling some file system reads as .git, is refused with push_unsafe_path. Delete that path in the session, then push again.
The push credential
A push uses an organization secret of purpose git, created by an admin:
ciresk dev secrets set github-push --purpose git --host github.com --repo github.com/acme/app < token.txt- For a GitHub forge the value is a token; for an ssh forge it is an openssh private key (a deploy key).
--hostbinds it to the forge's git host. When severalgitsecrets for that host reach the session, the first by name is used.--repo,--templateand--agent-sessionsselect which sessions get it, as for every secret. A session an agent, a service or a key started gets it only when the secret was set with--agent-sessions.- The session itself never sees it: a
gitsecret gets no placeholder in the session and never goes through the session's egress proxy. It is fetched for each push on the host and kept on no disk, and it is never in an answer, a log or an audit record. - No secret of purpose
proxymay name the forge's host: the session's own requests to that host would carry it, so the session could push with it around its branch. While one does, every push is refused withpush_credential_proxied; set that secret again with--purpose git, or remove it. - Give the token only what a push needs: contents and pull requests, write.
See secrets.md for creating, listing and removing secrets.
Who may push
The session's owner, or a member the owner shared the session with as use (see sessions.md). People, agents and services may call it, with the dev:sessions.attach scope. Agents push through the MCP tool dev_session_push, with the same base, pr and title arguments and an idempotency_key (see mcp.md). Only a person can create the git secret.
Refusals
| Code | Why | What to do |
|---|---|---|
push_unavailable | the installation has no forge for sessions (what every push answers in eu1 today), or the session was created before push existed, or its branch is not refs/heads/ciresk/<session>; the detail names which | push from your own checkout, or ask your installation's operator; for an old session, start a new one with ciresk dev up --remote |
not_cloud_writer | the session is local: its edits are your worktree's | git push from this worktree |
push_credential_missing | no secret of purpose git for the forge's host reaches this session | an admin runs ciresk dev secrets set <name> --purpose git --host <host> --repo <repo>, with --agent-sessions when agents start the session |
push_credential_proxied | a secret of purpose proxy names the forge's host | set it again with --purpose git; ciresk dev secrets list shows which |
push_unsafe_workflows | a workflow would run on the session's branch, or the change touches .github/workflows/ | see Workflows |
push_unsafe_path | the change holds a .git path | delete it in the session, then push again |
change_requests_unsupported | --pr on a forge that opens no pull requests (a plain ssh forge); nothing was pushed | push without --pr, then open the pull request on the git host |
no_base_commit | the session started from no commit (a project without git, or a tree uploaded without a base) | ciresk dev checkpoint; this session cannot push |
non_fast_forward | someone else pushed to the session's branch; nothing was pushed | fetch the branch the answer names and look at what landed; the session pushes again only once the branch is back at its last push, or deleted |
session_ended | the session stopped, was killed or moved to another host during the push | ciresk dev up --remote, then push again |
validation_failed | the worktree's remote is not the session's repository or not on the forge's host, the base is not a valid branch name, --pr came without a base, or the title is not one line of 1 to 256 bytes | fix the argument |
push_unauthorized | the forge refused the credential: expired, or without contents and pull requests write | set the secret again with a new value |
push_refused | the forge refused the push for another reason | read the detail |
forge_unavailable | the forge did not answer: down, or the installation lets no traffic out to it | try again in a minute; if it persists, ask the operator |
secrets_unavailable | the session's credential could not be fetched | try again |
push_failed | the commit could not be built | try again; the detail says which step failed |
A worktree with no git remote is refused before anything is sent: add one with git remote add origin <url>.
Without the forge
These work today, in eu1 too:
- A local session.
ciresk dev upsyncs your worktree into the session, and you pull, commit and push in your worktree with your own git. This is the way to get edits into git from Ciresk's cloud today. - Checkpoints.
ciresk dev checkpointsaves a remote session's workspace as a tree root and prints it, with the next step to run Ciresk CI on it (ciresk ci check --tree <root>). - Stopping and continuing.
ciresk dev stopcheckpoints a remote session and releases its compute;ciresk dev start, orciresk dev up --remoteafter the session ended, continues it from its last checkpoint.
For operators of an installation
The forge is a setting of the installation, not of your organization. It is off until the operator sets it on the hosts that run sessions:
| Variable | Values |
|---|---|
CIRESK_DEV_FORGE | github: GitHub with each organization's token; ssh: a git server reached with each organization's deploy key. Unset: no push |
CIRESK_DEV_FORGE_GIT_URL | the git URL, ending in /. github: https://github.com/ by default. ssh: required, an ssh:// URL |
CIRESK_DEV_FORGE_API_URL | github only: https://api.github.com by default; set both URLs for GitHub Enterprise Server |
CIRESK_DEV_FORGE_KNOWN_HOSTS | ssh only: the server's host keys |
A value that does not configure is logged at start and leaves pushes off.
The installation's egress.yaml names its rows by product, and three products concern dev:
| Product | What it covers |
|---|---|
dev-session | what a session itself may reach, narrowed by its template's egress rows (see egress.md) |
dev | the control plane's own calls; never a session's traffic |
dev-push | the push alone: the host's fetch of the base commit, the push of the session's branch and the pull request calls |
Turning push on means the variables above plus dev-push rows for the forge's hosts: with GitHub, github.com and api.github.com; with an ssh forge, the git server's host. Without the API row the pull request calls are refused and answer forge_unavailable. Never list the forge for dev-session instead: that opens it to every session's own traffic.