Configuration

forges.yaml

Every field in forges.yaml: the named forge accounts, how a repo picks one, the token sources, the address rules, and what the first release of the file does not do.

Added in 2.5

forges.yaml names forge accounts. An account records which forge a host runs and its web address, so kraft repo connect stops asking about a self-hosted host whose name does not say which forge it is. The file does not exist until you write it, and without it nothing changes.

config/forges.yaml
accounts:
  corp-gitlab:
    forge: gitlab                 # github | gitlab
    web_url: https://code.corp.example/gitlab
    default_for_host: true        # repos on this host use it unless they name another
  acme-gh:
    forge: github
    web_url: https://github.com

An account that talks to the forge's API names where its token comes from:

config/forges.yaml
accounts:
  corp-gitlab:
    forge: gitlab
    client: api                   # api | cli
    web_url: https://code.corp.example/gitlab
    ca_file: /etc/ssl/corp-ca.pem # optional
    default_for_host: true
    token: { from: git-credential, kind: unknown }
  acme-gh:
    forge: github
    client: api
    web_url: https://github.com
    token: { from: file, file: ~/.kraft/secrets/acme-gh, kind: pat }
FieldTypeDefaultMeaning
forgegithub or gitlabrequiredThe forge the account is on. It must agree with the forge of each repo that uses it.
clientcli or apicliHow Kraft talks to the forge. cli keeps the sign-in of gh and glab, and refuses token and ca_file. api reads a token from the source token names.
web_urlhttps:// addressrequiredThe forge's web address. A trailing / is dropped. http:// is refused.
api_urlhttps:// addressderivedThe API address. It must have web_url's origin (host and port), except api.github.com for github.com and api.SUBDOMAIN.ghe.com for SUBDOMAIN.ghe.com.
tokenmappingfrom: git-credentialAn api account only. Where the token comes from, and its kind. See the tables below. A bare string is refused: the file never holds a token.
ca_filepathnoneAn api account only. A PEM file of certificate authorities to trust for the forge, for a private CA.
default_for_hosttrue or falsefalseRepos on this web host use the account unless their entry names another. Only one account may be the default for a host.

An unset api_url is https://api.github.com for github.com, https://api.SUBDOMAIN.ghe.com for a ghe.com host, https://HOST/api/v3 for another GitHub host, and WEB_URL/api/v4 for GitLab.

Where the token comes from

token.from names one source, and the one key that source needs. Any other key is refused.

fromKeyWhat it stores and where it runs
git-credentialnoneNothing. Kraft runs git credential fill, asked by host only, and never prompts. It reuses what your git helper holds.
commandcommandNothing. Set only with kraft admin forge or connect's option 3 on the server's machine, or by hand: never over HTTP. The command runs without a shell and its stdout is the token. Wrap a pipe in sh -c.
envenvNothing. The variable is taken out of the server's environment when it starts, so a change needs a restart.
filefileThe token, in one file only you can read. The path starts with /, ~/ or $. A pasted token (--token-stdin) goes to secrets/NAME under Kraft's home.

A file path may use ~ and $VAR; an unset variable is refused, naming it. The file is refused when it is a symlink, is not owned by the user the server runs as, or has any group or other permission bit. It is also refused inside a connected repo, a worktree, a sandbox.expose root, /etc, or a system directory every sandbox mounts.

Settings > Forge accounts sets every source but command, and a pasted token goes to the same secrets/NAME file.

A sandboxed worker never sees the file. A host (unsandboxed) worker runs as the same user as the server, so it can read the file.

An env name may not be one Kraft needs: KRAFT_*, GIT_*, PATH and the other variables every child gets, or ANTHROPIC_API_KEY and the other credentials Kraft passes to agents.

token.kind says what the token is. Every kind is sent as Authorization: Bearer.

kindMeaning
patA personal or project access token.
oauthAn OAuth access token.
unknownThe default. Kraft does not say what it is.

The origin rule

api_url must be on web_url's origin: the same host and the same port, where no port means 443. An api_url on another port is refused. The two exceptions are api.github.com for github.com and api.SUBDOMAIN.ghe.com for SUBDOMAIN.ghe.com, where both addresses must be on 443.

Git push and fetch over HTTPS

On an account with client: api and a command, env or file token, git push, fetch and the git-lfs upload to an https:// remote at the account's address send the token in a header set for that one call. It is never written to .git/config and no hook sees it. The user name is x-access-token on GitHub and oauth2 on GitLab.

git-credential accounts and SSH remotes are unchanged: git asks its own credential. So do the repo library check (kraft repo library) and the git-lfs downloads, at checkout and in the fast-forward that brings the reviewed head home.

A push follows the account the item is pinned to. A fetch follows the account configured now. A push URL on another address is refused, and so is a push when any one of several push URLs is. Kraft never sends the token there.

A base fetch the token cannot authenticate stops the item and names the account, instead of starting from a stale base. Redirects are never followed with the header: a redirect stops the item, and the fix is to point origin at the new address. The stop card still shows the token hint.

For these calls http.sslVerify is forced on and GIT_SSL_NO_VERIFY is ignored. The CA git trusts is the account's ca_file, else GIT_SSL_CAINFO or GIT_SSL_CAPATH, else the system or global gitconfig's http.sslCAInfo, else the CA bundle Kraft ships (certifi). A CA set only in a repo's own .git/config is ignored.

A corporate CA that lives only in the macOS keychain is not seen: set the account's ca_file, or http.sslCAInfo in your global gitconfig, or these calls stop with a certificate error that names the account. GIT_TRACE variables are removed and trace2 is switched off.

The repo's local git config on the server's machine is inside the trust boundary: whoever can write it can read the token source directly. A sandboxed worker cannot write it (the common gitdir's config is mounted read-only); an unsandboxed one runs as you. Repository content is outside it, which is why a base .lfsconfig is checked. Kraft pins push.gpgSign and core.alternateRefsCommand, refuses git-lfs custom transfer agents, and pins the routing keys as defence in depth.

Kraft pins the header only at the exact URL git dials, and redirects, sslVerify, the CA, the proxy (including remote.origin.proxy) and the resolve list at those and at the origin. It dials origin by name, runs no signer, alternate-refs command, bundle fetch or ssh with the header, and refuses an origin URL that names a user, a remote helper, a push URL that differs from the fetch URL, and a git-lfs custom transfer agent.

A push with LFS objects is refused when lfs.url, lfs.pushurl, remote.origin.lfsurl or remote.origin.lfspushurl is set, or when the base branch's .lfsconfig names an endpoint other than origin's own. git-lfs matches http.<url> keys against the remote URL only. git-lfs routing to a non-loopback host is unverified: the tests run against loopback.

Which account a repo uses

A repo uses the account its repos.yaml entry names with forge_account. Otherwise it uses the account marked default_for_host for the host of its origin remote, when that account's forge is the repo's forge; a default for another forge is skipped, and doctor says so. Otherwise it uses the CLI as before.

Hosts compare in lowercase, without a port or a trailing dot. An SSH alias such as github-work is not the account's host, so a repo with one names its account itself.

A file that does not load

A file that does not load is refused when the server rereads it: the running accounts are kept, /health reports degraded with invalid_forges, and kraft admin doctor names the file and the key, or for a file that is not valid YAML, the line and column. kraft admin reload rereads it.

A file that does not load when the server starts leaves no accounts to keep. Items already pinned keep their pins.

Every repo not yet pinned stops at its next forge node, until the file loads and a person retries it: with forges.yaml does not load (<why>); fix it, then a person's `kraft item retry` , or for a repo that names an account, forge_account <name>: forges.yaml does not load (<why>). Its forge reads, labels and merge request calls are refused the same way. A missing file is not this case: it means no accounts.

invalid_forges also names a repo whose forge_account names no account, or an account on another forge. A save that fixes it clears it.

How Kraft talks to GitLab over the API

On a client: api GitLab account, Kraft runs every forge node and forge read over GitLab's REST API with the account's token. It does not run glab. It works on gitlab.com and on a self-hosted GitLab on any domain or path.

The project comes from the repo's push URL as configured: remote.origin.pushurl, else remote.origin.url, before any insteadOf rewrite. An https:// URL loses the path prefix of web_url, so https://code.corp.example/gitlab/group/sub/app.git is the project group/sub/app. An SSH URL keeps its path. An https:// push URL on another host than the account's is refused, without quoting it.

Kraft reads each account's token once and keeps it in the server's memory. A request answered 401 reads the token again and retries once, only if it changed. An env source is never read again, so a new value needs a restart. kraft admin reload clears the cache, and so does saving an account in Settings.

Each item uses the backend and account its first forge node pinned, and each repo of a workspace is run from its own pin. A repo pinned to glab keeps running glab under a workspace root on an API account.

A failed job's log is read as its last part: a log past the size cap keeps its end. A very large log can still time out, and the item's log route then answers 502. Jobs of a pipeline are read a page at a time, so one with up to 1,000 jobs is no longer cut short, on either backend.

GitLab versions

Kraft reads the server's version for the last two calls. For recent merged merge requests it does not: it asks for merged_at and, on a 400, asks again sorted by updated_at.

CallNewer GitLabOlder GitLabVersion unreadable
Recent merged merge requestsorder_by=merged_at, from 17.2.Sorts by updated_at, after a 400.Same as either, by the answer.
Merge when the pipeline passesauto_merge, from 17.11.merge_when_pipeline_succeeds.auto_merge, as glab sends it.
Cancel an auto-mergecancel_auto_merge, from 19.5.The deprecated cancel endpoint.The deprecated endpoint.

How Kraft talks to GitHub over the API

On a client: api GitHub account, Kraft runs every forge node and forge read over GitHub's REST and GraphQL APIs with the account's token. It does not run gh. It works on github.com, on GitHub Enterprise Server and on ghe.com.

The repository comes from the repo's push URL as configured, before any insteadOf rewrite, and is exactly owner/name. Any other path, such as a/b/c, or an https:// URL on another host than the account's, is refused without quoting the URL.

On Enterprise Server, GraphQL is at https://HOST/api/graphql.

A job log lives in GitHub's storage, so GitHub answers its request with a redirect. Kraft follows that one https redirect without the token or GitHub's headers, and refuses a second. A failure there names the storage's host, never the signed address, and is not read as a token problem. The redirect and the download share one timeout, so a log read takes no longer than a single call.

The download trusts the system's certificate authorities and the account's CA together: its ca_file, else git's http.sslCAInfo.

What the token needs

A classic personal access token needs the repo scope. A fine-grained one needs access to the repository, with the permissions GitHub lists for the REST endpoints Kraft calls: see Permissions required for fine-grained personal access tokens. Kraft reads and writes pull requests, labels, check runs, commit statuses and Actions job logs.

An organization with single sign-on refuses a token until you authorize it. The stop names the account and carries the address of GitHub's authorize page; open it, authorize the token, then retry.

A workflow's GITHUB_TOKEN or a GitHub App installation token can run items, but it is not a supported kind. The probe asks GET /user, which GitHub answers 403 to such a token, so kraft admin forge test reports it as forbidden and /health stays degraded.

What this release does not do yet

  • forges.yaml is carried across a major update unchanged, like workers.yaml.
Copyright © 2026