The problem
Merging is not shipping. After the merge somebody still has to decide whether there is a release in it, pick the SemVer bump, write the changelog, check the dependency audit, cut the tag and publish. On a small team that work is manual, inconsistent and easy to skip — and handing it to an autonomous agent that tags and publishes on its own is the opposite failure: nobody approves the step that is hardest to undo.
What Postmerge does
Postmerge is a GitLab Duo Agent Platform custom flow for an Expo / React Native app. Started from an issue comment once main is green, it:
- Guards — a deterministic script decides, before any model turn, whether the run should continue. Wrong branch, a release already waiting for its tag, an open Release MR, or nothing releasable all end the run for free.
- Gathers facts — scripts compute the commits since the last tag, the SemVer bump and the dependency audit (
npm audit, zero known advisories allowed). - Judges — a model step reads those facts and answers
releaseorskip. - Prepares — a script creates
release/vX.Y.Z, bumps the app version, writes the CHANGELOG section and pushes. - Publishes — a model step opens the Release MR and a Release issue that explain the scope, the SemVer rationale, the audit, the risk and the two gates.
Then it stops. Gate 1: a human merges the Release MR. Gate 2: a human runs the manual release:tag job. The tag pipeline packages the web build into the GitLab package registry, creates the GitLab Release with the changelog as notes, deploys to GitLab Pages, and a smoke job checks that the deployed version matches the tag.
The deployed app is a release receipt: the version, what shipped (from the CHANGELOG), how it shipped (the flow draft, both gates, links to the MR, Release and pipeline) and a live check that the served version matches the build — all generated at build time from the repository.
Why Assisted
The flow proposes; a human approves every irreversible step. The flow never tags, merges or publishes — no tool it holds can. That is a design choice: release is the step where an unattended mistake is hardest to undo.
Security by construction
- No model step holds
run_commandorcreate_commit. Every command in the flow is a literal; no model output, commit message or log line ever becomes a shell argument. - No gate rests on a model answer. The judge reads untrusted commit text, so its
releaseis advisory: the prepare script re-runs the audit and the bump check before writing anything. - A resumed release is accepted only if the branch matches, byte for byte, what the trusted scripts rebuild from its parent — a hand-pushed or tampered
release/*branch is never published. - Zero known advisories ship: audit-ci fails on any severity with an empty allowlist; vulnerable transitive packages are fixed, never allowlisted.
- The flow runs in GitLab's sandbox with an explicit network policy (
.gitlab/duo/agent-config.yml).
Proven end to end
Two full cycles ran on the project: v1.1.0 and v1.2.0 (the one in the video). Each: flow session → Release MR + issue → gate 1 → gate 2 → green tag pipeline → GitLab Release → Pages → smoke check. Proof for each of the nine DevSecOps stages is linked in docs/STAGES.md.
How we built it
Probe flows first: before designing the release flow we ran small flows that measured how the Duo runtime actually behaves (step output shape, routing, sandbox egress, token variables). Every routing decision in the final flow rests on a measured fact, recorded in the spec under docs/features/postmerge-release-flow/.
Challenges
- Routers match the whole
"Exit code: N\n<output>"string, so every routing script prints exactly one token with no newline. - Node's
fetchignores the sandbox proxy, so the MR lookup goes throughglab, thencurl. - Custom flows reject
max_cyclesand injectgoalinto components without explicit inputs.
What's next
Turn on the "Pipeline passed" trigger so a green main starts the flow without a comment; protect release/* and v* refs.
About the video
Every shot is a screen recording of the real v1.2.0 cycle, assembled in Remotion. Narration: synthetic voice (Amazon Polly).
Built With
- expo.io
- gitlab-ci
- gitlab-duo-agent-platform
- gitlab-package-registry
- gitlab-pages
- node.js
- python
- react-native
- remotion
- typescript
Log in or sign up for Devpost to join the conversation.