Skip to main content
The CI lane lets a GitHub Actions workflow push project files without storing a long-lived Metabind credential in your repository. GitHub proves which repository a workflow run belongs to using its own OIDC token, and Metabind exchanges that for a project-scoped access token, good for 15 minutes.
With CLI 0.10.5 and the current server, metabind publish fails with HTTP 403 when using a CI token. The command lists components before publishing, but the token lacks the required read:components scope. The token includes publishing permissions, but those do not authorize this prerequisite read. The workflow below pushes draft changes only; publish separately from Studio or a signed-in local CLI session until the server grants the missing scope.
Start with Sync Your First Project to see how pushing and publishing work before wiring the push step into CI.
The CLI ships as a macOS binary today. The job needs a macOS runner (runs-on: macos-latest) — an ubuntu-latest or windows-latest runner cannot install it.

Why This Instead of an API Key

A long-lived API key sitting in a repository secret is the credential that actually leaks in practice: it outlives whoever created it, gets copied into forks, and nothing expires it. The CI lane replaces that with a token that:
  • is minted fresh for every workflow run and never stored anywhere,
  • expires in 15 minutes,
  • can edit existing components, tools, and content through push, but can’t add new ones with CLI 0.10.5, manage users, mint API keys, or change project settings — including the binding that grants this capability.

Bind a Repository to a Project

Tell Metabind which repository is allowed to obtain a CI token for a project. There’s no dedicated CLI verb for this yet, so set it through a project update:
Without a ref, any branch pushed to the bound repository can obtain a CI token — a pull request from a fork does not receive a token, but any branch pushed within the repository does. Pin ref to your release branch if you don’t want that.
Include repository every time you update ci: an update that sends only {"settings":{"ci":{"ref":"..."}}} is rejected. Fields you omit keep their stored value, so leaving out ref doesn’t remove an existing branch pin. Send "ref": null to remove the pin, or "ci": null to remove the binding.

Example Workflow

A few things this depends on:
  • permissions: id-token: write at the job or workflow level. Without it, core.getIDToken() fails, and the failure reads as a missing function rather than a missing permission.
  • The exchange response is wrapped in data — read data.accessToken, not a top-level accessToken.
  • --org and --project on each CLI command. A runner has no saved context, so without them push stops before sending a request.
  • core.setSecret() masks the token in the job log. It expires in 15 minutes regardless, but there’s no reason to leave it unmasked.
  • Only edits to entities that already exist. A push that includes a file with no recorded id in .metabind/state.json lists components first, and that read fails with 403 because the token lacks read:components. Add new files from a signed-in session.
  • A current .metabind/state.json in the repository. push doesn’t update it and the CI token can’t run pull, so after the workflow pushes, run metabind pull in a signed-in session and commit the refreshed state. Otherwise the next change to an entity the workflow already pushed is refused as a conflict.
  • No project settings changes. The token can’t write metabind.jsonc, mcp-instructions.md, or agent/, so a push that includes one fails with 403 after writing the other changes. Push settings changes from a signed-in session.
After reviewing the pushed drafts, publish from Studio or run the following locally in a session authenticated with metabind auth login, with MB_TOKEN unset so the CLI uses your login:

Diagnosing a Rejected Exchange

Every rejected exchange returns the same generic denial, on purpose — a more specific error would let a caller enumerate which repositories are bound to which projects. When an exchange is denied, check in order:

What the Token Can Do

A push writes component source, so the token can change the code a release is built from. It can’t re-point the binding at a different repository even if the token itself leaks.

Preview Projects for Pull Requests

An ephemeral preview gives every pull request its own project to test against, without anyone having to remember to clean it up. The CI token can’t create or delete previews, because it only works against the project it was minted for, so run these commands signed in with metabind auth login:
  • The copy is shallow: current draft state only, no version history and no published packages.
  • Every preview expires — there’s no option to create one that doesn’t. The default is 48 hours, up to a maximum of 7 days.
  • Delete it explicitly when the pull request closes:
preview delete refuses to run against a project that isn’t marked as a preview, so a wrong id won’t take down a real project.

Next Steps

Flat-File Sync

Pull a project to disk, edit it, and push changes back.

Workflow Patterns

Build, publish, and roll back changes with the CLI.