Also in: Español, Português, 日本語
Disclosure: we build Carbon Forge at 100monkeys.ai, so read this as a maker's account. This guide was drafted by one of our AI agents from the public docs, chiefly the "Cut a release" and "Publish to git" sections of Code and files on the substrate, the Archives catalog page and Source control settings, and reviewed by the company. Every call is the docs' own example. Where the docs leave a question open, the last section says so.
The model in two sentences
"A cut names the whole workspace's state at one moment and never moves." "The git archive renders a code tree as git: one commit per tree revision, whose message is the landing's intent, and an annotated tag refs/tags/nn/<cut> per cut, whose message carries the cut's notes." Because it is rendered "from the record byte for byte, so the same revision always renders to the same commit", the GitHub copy can be checked against the record at any time.
Step 1. Land the work the release should hold
A cut names the trunk, so anything still on a lane is not in it. Land what belongs in the release first, with intents that start feat, fix, docs, chore or revert: that first word is how the notes group each landing (Land a code change and its documentation in one changeset walks one landing).
Step 2. Cut the release
snapshots.cut { "name": "v1.4.0" } The cut is permanent. The paradigm page says "the database refuses to change or remove a snapshot or a cut while the workspace exists." snapshots.materialize reads any entity as the cut holds it, and trees.diff and trees.grep take a snapshot too.
Step 3. Read the release notes
cuts.notes { "from": "v1.3.0", "to": "v1.4.0" } cuts.notes "lists every changeset landed between the two cuts, grouped by the first word of its intent", with the lane each landed from and the paths it touched; path_glob narrows it to one part of the workspace. In the app, /<workspace>/cuts/<name> opens on the same notes, headed Notes from <previous cut>, and Compare with another cut picks any two.
Step 4. Connect a GitHub account to the instance
The push goes "as the instance's GitHub App", so the App must be installed on the account that owns the repository. An instance admin opens Settings → Instance → Source control and presses Connect a GitHub account, which sends the browser to "GitHub's install page for the CarbonForge.dev GitHub App". There you pick the account or organisation and the repositories. The permissions the docs list:
- Contents: Read and write, to push commits,
refs/heads/mainand the cut's tag. - Workflows: Read and write, needed only when the push adds or changes a file under
.github/workflows/; without it "GitHub refuses". - Actions: Read, for reading the remote's runs in Step 8.
Back in Carbon Forge the tab lists Connected to @<account>.
Step 5. Render and verify the git archive
archives.render { "tree": "app", "cut": "v1.4.0" }
archives.verify { "tree": "app", "cut": "v1.4.0" } Render writes the tree's commits and the cut's tag into the archive; verify reports the archive's status and, when there is one, the first difference. On the cut's page, a workspace admin has the same Render and Verify buttons, and each answer is shown verbatim: Rendered: tag <id>, Verify: <status>.
Step 6. Set the remote
archives.set_remote { "tree": "app", "remote": "https://github.com/acme/app.git" } The remote is written https://github.com/<owner>/<repo>.git. It is also each repository's Remote field in Settings → Workspace → Source control. Setting it needs workspace admin, "because the remote decides where the archive is published".
Step 7. Push
archives.push { "tree": "app" } "The push sends the remote only what it lacks, fast-forward only, as the instance's GitHub App, and refuses rather than move main backwards or move a tag." It is queued as a job (Push queued as job <id>.). The refusals name their cause, for example The tree <path> has no archive remote; set one with archives.set_remote, and nothing was pushed., or one that says the archive "has no rendered cut". A refusal from GitHub comes back as The remote refused <ref>: <reason>. with GitHub's own reason.
"The remote is a copy for git tooling and readers; nothing is read back from it." Work continues in Carbon Forge, and the next cut pushes again.
Step 8. Read the deploy back
"Once a cut is pushed, its deploy can be read back": archives.deploy_status answers the remote's GitHub Actions runs for the cut's commit, at most 100, with a summary, "and the record still takes nothing from the remote." The cuts index marks each tree pushed, tagged or not rendered. A webhook on cut.taken or archive.pushed tells a deploy script without polling.
What the docs leave unanswered
- A first push to a repository that already has history. The push is fast-forward only and refuses to move
mainbackwards; the docs describe an opt-in history rewrite but do not walk the case of an existing, unrelatedmain. - Hosts other than GitHub. Every remote in the docs is a GitHub URL.
- Whether a self-hosted deployment can use the CarbonForge.dev App, or must register its own; the settings page names the operator settings but not that choice.