Git
Every project is a git repository: clone it, push to it with any git client, follow each session's branch, and land work through change requests.
Every Kortix project is one git repository. You clone it, push to it with any git client, fetch the branch of each session, and land work on the base branch through change requests. This page is for everyone who works on project files outside the web app, and for admins who decide who may push.
The shortest path from a project to a reviewed change:
kortix projects clone <project_id> my-project
cd my-project
git checkout -b tighten-triage
# edit agents/, skills/ or kortix.yaml
git commit -am "Tighten the triage prompt"
git push origin tighten-triage
kortix cr open --head tighten-triage --title "Tighten the triage prompt"
Your project’s repository
A project has one of three kinds of repository. All three work the same way after the project exists.
| Kind | What it is | How you get it |
|---|---|---|
| Managed | Kortix creates and manages a private repository. You need no GitHub account. | Pick Kortix managed under Git account when you create a project. Or run kortix ship in a folder with no origin remote, or kortix ship --origin managed. |
| GitHub | A repository in your GitHub account or organization. Kortix reaches it through the Kortix GitHub App, which you install under Account settings → Git, or through a GitHub token. | Pick a GitHub account under Git account, then Create a new repository or Import an existing repository. Or run kortix ship in a folder whose origin is on GitHub. |
| Other git host | A repository at any other git URL. | Run kortix ship in a folder with that origin, or kortix ship --origin <git-url>. |
On a self-hosted instance, a platform admin sets up managed repositories in the admin console. GET /v1/projects/managed-git/status returns configured: false until then, and the new-project form says “Kortix-managed repositories are not available on this instance.” See Self-hosting.
The git origin
Every project has one clone URL, whatever its kind:
https://<api-host>/v1/git/<project_id>.git
On Kortix Cloud this is https://api.kortix.com/v1/git/<project_id>.git. Find it in two places:
kortix projects info <project_id> --json, fieldgit_origin_url.- Customize → Settings → Git repo → Use your own Git client → Clone address.
Clone git_origin_url, not repo_url. repo_url is the upstream repository that Kortix forwards to.
- The origin speaks git over HTTPS only.
- You authenticate with a Kortix token. Kortix adds the upstream credential on the server. No GitHub or other provider credential reaches your computer or a sandbox.
- The audit log records each clone and fetch as
git.cloneand each push asgit.push. See Audit actions.
Clone a project
Install the CLI and sign in
Follow CLI → Install, then run kortix login.
Clone
Run kortix projects clone [<project_id>] [<dir>]. kortix projects ls lists project ids. Without an id, the command clones the linked or default project. The directory defaults to the project id, not the project name.
Finish setup in the new folder
Optional: run kortix init --force to configure your local coding agents in the checkout. Run kortix env pull to write a .env file with one line per project secret. It writes names only: secret values stay in Kortix.
Customize → Settings → Git repo → Work on this locally shows the same three steps with the commands filled in.
The clone does three things:
- It runs
git clonewith your token in the process environment. The token is never in the remote URL or in.git/config. - It writes
.kortix/link.json, which binds the folder to the project forkortixcommands. It adds the file to.git/info/exclude, so the link stays on your computer. - It installs a credential helper for the origin, in the repository’s own
.git/config:
[credential "https://api.kortix.com/v1/git/<project_id>.git"]
helper =
helper = !kortix git-credential
[credential]
useHttpPath = true
The empty helper = line turns off every helper you configured elsewhere for this URL, so your keychain never stores the Kortix token. kortix git-credential gives git the token of your stored login each time git asks. After the clone, plain git pull and git push work. If that login is gone, git stops with Kortix Git needs login. Run: kortix login, plus --host <name> when the link names a host.
The clone writes the link and the helper only when the repository root has .kortix/, kortix.yaml, kortix.yml or kortix.toml. In any other repository, create a .kortix/ folder at the root, then wire them by hand as in Wire the helper by hand.
Use any git client
The origin accepts HTTP Basic authentication. The username can be anything; the convention is x-access-token. The password is a Kortix token.
git clone https://api.kortix.com/v1/git/<project_id>.git
# Username: x-access-token
# Password: kortix_pat_…
Plain git saves the password in the credential store you configured. To keep the token out of every store, send it as a header on each command instead:
git -c http.extraHeader="Authorization: Bearer $KORTIX_GIT_TOKEN" clone https://api.kortix.com/v1/git/<project_id>.git
| Token | What it can do |
|---|---|
Personal access key, kortix_pat_…, from kortix login, Settings → Personal access keys or kortix tokens new |
Acts as you. Kortix checks it against your role on the project. Membership of the account alone gives no access. |
Project CLI token, kortix_pat_…, from kortix projects cli-tokens new |
Acts as you, on one project only. |
Session token, KORTIX_TOKEN inside a sandbox |
Reads the repository and pushes the session’s own branch. See Who can clone and push. |
Service account key (kortix_sa_…), OAuth token, gateway key, browser sign-in token |
Refused with 401. |
Sign-in, SSO & API keys explains how to create and revoke keys.
Wire the helper by hand
Run these at the root of the checkout. kortix projects link refuses a folder without .kortix/ or kortix.yaml. The helper answers only for the URL that equals the project’s git_origin_url.
ORIGIN=https://api.kortix.com/v1/git/<project_id>.git
git config --local --replace-all credential.$ORIGIN.helper ''
git config --local --add credential.$ORIGIN.helper '!kortix git-credential'
git config --local credential.useHttpPath true
kortix projects link <project_id>
kortix projects link writes .kortix/link.json. The helper reads it to find the project and the host.
From CI
Create a project CLI token with kortix projects cli-tokens new and store it as a CI secret. The token acts as the person who created it, so that person needs push access to push.
AUTH="Authorization: Bearer $KORTIX_GIT_TOKEN"
git -c http.extraHeader="$AUTH" clone https://api.kortix.com/v1/git/$PROJECT_ID.git project
cd project
# change files, then commit
git -c http.extraHeader="$AUTH" push origin HEAD:refs/heads/ci-update
Errors from the origin
| Status | Message | Fix |
|---|---|---|
400 |
invalid project identifier |
Use the project id, a UUID, in the URL. |
401 |
authentication required, with WWW-Authenticate: Basic realm="Kortix Git" |
Send a token. Git prompts for one. |
401 |
git proxy requires a Kortix token |
The password is not a Kortix token. Use a kortix_pat_ token. |
403 |
token is not authorized for this project |
You have no role on the project that allows the operation. See Who can clone and push. |
403 |
token is scoped to a different project |
The token is bound to another project. |
404 |
Not found |
The project does not exist or was deleted. |
409 |
Repository changed during authorization; retry the request |
Someone replaced the repository while you connected. Retry. |
502 |
No git upstream is configured for this project or git upstream unreachable |
Kortix cannot reach the upstream repository. Check the connection under Git repo. |
503 |
git_credential_unavailable |
Kortix could not get a credential for the upstream. Retry when the body says retry: true. Otherwise check the connection. |
Commit and push
After a clone, commit and push with plain git. kortix ship runs these steps in one command:
- It runs the
kortix validatechecks.--no-verifyskips them. - It runs
git add -Aand commits, with the message from-m. The default message iskortix: ship.--no-commitfails when the working tree has changes. - It offers to set each
envsecret that has no value.--no-envskips this. - It pushes the current branch to the branch of the same name.
- It connects each declared connector that still needs sign-in.
--no-connectskips this.
-n / --dry-run prints the plan and changes nothing. kortix deploy is an alias.
The first ship
A folder with no .kortix/link.json gets a new project. Ship picks the repository from the folder’s origin, without a prompt:
origin of the folder |
What ship creates |
|---|---|
| A GitHub URL | A GitHub project through the Kortix GitHub App. If the App is not installed, ship prints an install link and asks you to retry after you install it. With -y it stops instead. --github-token <PAT> links without the App; the token needs repository Contents read and write. |
| Another URL | A project for that URL. |
None, or --origin managed |
A managed repository. Ship sets origin to the git origin and installs the credential helper. |
For a GitHub or other URL, the first ship pushes to your existing origin with your own git credentials.
--origin <git-url> sets origin to that URL first. --account <id|slug> picks the account, and --name the project name.
Ship writes .kortix/link.json the moment the project exists, and git add -A commits it. A teammate with push access (project.gitops.push, the Manager role) who clones the repository can then run kortix ship too. Later ships set origin to the project’s git origin and install the credential helper.
Who can clone and push
Five permissions control git:
| Permission | Allows |
|---|---|
project.gitops.read |
Clone and fetch every branch. |
project.gitops.push |
Push. Open change requests. |
project.gitops.merge |
Merge change requests. |
project.gitops.ref.any |
For a session: push a branch other than its own. |
project.gitops.ref.delete |
Delete a branch. |
A project Member holds project.gitops.read only: a Member can clone but cannot push. A Manager holds all five. Account owners and admins act as Managers on every project in the account. See Accounts & access.
People with push access:
- may push any branch, including the default branch;
- need
project.gitops.ref.deleteto delete a branch; - can never delete the default branch. Nobody can.
IP allowlist and MFA policies of the account apply to git requests too.
Sessions push with the session token:
- A session pushes only its own branch, the branch named after its
session_id. - Any other branch needs
project.gitops.ref.anyin the agent’skortix_permissions. Deleting a branch needsproject.gitops.ref.delete. - A push to the default branch also needs
project.agent.write,project.trigger.create,project.trigger.updateandproject.trigger.delete. - The agent’s
kortix_permissionsmust list the permission, or beall, and the agent’s ceiling role must also hold it. Neither widens the other. The role of the person who started the session is not an input. An agent with nokortix_permissionskey holds no project permission. See What an agent may do.
By default, agents land work through change requests. Agents & permissions shows how to grant leaves.
Kortix does not block a force-push. On a GitHub repository, add a ruleset that blocks force-pushes and deletions on the default branch. The ruleset does not block change-request merges, because a merge is a fast-forward or a merge commit.
Read a refused push
Kortix refuses the whole push when it refuses one ref. Git prints the reason for each ref:
! [remote rejected] HEAD -> feature-x (a session may only push its own branch (<session_id>); commit there and open a change request to land this elsewhere)
| Reason | Cause |
|---|---|
<branch> is the default branch and cannot be deleted |
A delete of the default branch. |
deleting <ref> requires git ref-delete authority |
A person without project.gitops.ref.delete deleted a branch. |
a session may only push its own branch (…) |
A session without project.gitops.ref.any pushed another branch. |
pushing <branch> skips change-request review and needs agent and trigger write permission; … |
A session pushed the default branch without project.gitops.ref.any and the manifest permissions. |
pushing <branch> could change agent grants without review, and an agent grants only what it holds; … |
A governed agent pushed the default branch but does not hold every permission it could grant. |
a session may not delete its own branch |
A session without project.gitops.ref.delete deleted its branch. |
Session branches
Each session works on its own branch. The branch name is the session_id, with no prefix. Kortix cuts it from the session’s base_ref, which is the project default branch unless the session was created with another one.
Inside the sandbox, the branch is $KORTIX_BRANCH_NAME. The agent commits there and runs git push origin HEAD. Sandboxes & previews lists the other variables.
A pushed branch stays in the repository after the session stops and after the session is deleted. Work the agent did not push is lost when the sandbox is deleted.
Get an agent’s work on your computer:
git fetch origin <session_id>
git checkout <session_id>
kortix files brancheslists the branches with commits ahead of and behind the default branch.kortix sessions info <session_id>showsbranchandbase_ref.- To start a session on another branch, pass
base_refwhen you create it, inPOST /v1/projects/<project_id>/sessionsor insessions.create()of the SDK.kortix sessions newhas no flag for it.
When the base branch moves, open the command palette in the session and run Ask Agent: Sync Branch & Reload. The agent merges origin/<base_ref> into the session branch, resolves conflicts, runs the relevant tests, and commits. Its last command is kortix sessions reload … --no-repo --force --yes, which restarts the agent runtime and ends the turn. Send continue when the runtime is back.
Change requests
A change request merges one branch, the head, into another, the base. Kortix runs the merge, the diff and the conflict check as git operations on the project repository, so change requests work on every kind of repository.
open ──merge──▶ merged (final)
open ──close──▶ closed ──reopen──▶ open
Open one from your computer
git push origin my-branch
kortix cr open --head my-branch --title "Add the triage agent" --description "Why and what changed"
- The base is the project default branch.
--base <branch>targets another branch. - A head with no commits ahead of the base is refused with
CR_HEAD_NOT_AHEAD. Push first. - Each later push to the head branch updates the same change request.
Open one from a session
An agent follows this contract:
- Commit on
$KORTIX_BRANCH_NAME. - Push:
git push origin HEAD. - Open:
kortix cr open --title "…" --description "…". Inside a sandbox,--headand--sessionfill themselves from$KORTIX_BRANCH_NAMEand$KORTIX_SESSION_ID. - Report the change request number to the person, and stop.
A change request from a session targets the session’s base_ref. An agent cannot retarget it: --base with another branch returns 400 CR_BASE_NOT_SESSION_BASE. A person can open a change request for the same head with --base.
Review and merge
Review in the web app on the Review page, or from a terminal:
kortix cr ls # open change requests
kortix cr diff 3 # the unified diff
kortix cr merge-preview 3 # can it merge? lists conflicts
kortix cr merge 3
kortix cr request-changes 3 --message "Rename the flag first"
A merge runs these steps:
- Kortix validates
kortix.yamlon the head branch, with itsimports:. An invalid manifest stops the merge with422MANIFEST_INVALID. A head with no manifest passes. - Kortix fast-forwards the base when the head is strictly ahead.
- Otherwise it creates a merge commit,
Merge CR #<n>: <title>, authored byKortix <noreply@kortix.ai>.--messagesets another message. - A conflict stops the merge with
409and the list of conflicting files.
After the merge, running sessions on that base pick up the new config, and Kortix syncs the project’s connectors from the new kortix.yaml. See Config comes from the base branch.
Request changes records the note on the change request and sends it to the session that opened it as a new prompt. A sleeping sandbox wakes.
Agents that merge
- An agent that declares
kortix_permissionsmerges only when the grant includesproject.gitops.merge. An agent withoutkortix_permissionscannot merge. Only themetacoordinator and a project on the legacy v1 manifest with no agents declared act with the role of the person who started the session. - A session cannot merge a change request it opened unless its grant in
kortix.yamlincludesproject.gitops.merge. Without it, the merge returns403CR_SELF_MERGE_REFUSED. - A merge that changes
agents,default_agentortriggersalso needs the permission the direct edit needs:project.agent.write, orproject.trigger.create,.updateor.delete. The merging agent cannot grant any permission it does not hold.
A grant that names project.cr.open or project.cr.merge counts as project.gitops.push or project.gitops.merge. Write the gitops name in new grants.
Permissions
| Action | Needs |
|---|---|
| List, show, diff, merge preview | Read access to the project. |
| Open | project.gitops.push |
| Edit, close, reopen | Write access to the project. An agent also needs project.gitops.push. |
| Merge | project.gitops.merge, plus the manifest permissions above for an agent. |
| Request changes | project.review.act |
kortix cr commands
<cr> is the change request number, such as 3, or its UUID.
| Command | Does |
|---|---|
kortix cr ls [--status <s>] |
Lists change requests. <s> is open (default), merged, closed or all. |
kortix cr show <cr> |
Shows one change request. |
kortix cr diff <cr> |
Prints the unified diff. |
kortix cr open --head <branch> --title <t> [--base <branch>] [--description <t>] |
Opens a change request. Aliases: --from, --into, --body. |
kortix cr merge-preview <cr> |
Shows whether it can merge, and the conflicts. |
kortix cr merge <cr> [--message <t>] |
Merges it. |
kortix cr request-changes <cr> --message <t> |
Sends the note to the agent that opened it. |
kortix cr close <cr> |
Closes it without a merge. |
kortix cr reopen <cr> |
Reopens a closed one. |
kortix cr version-diff --from <head> --into <base> |
Summarizes a merge before you open a change request. |
Each command takes --project <id> and --host <name>. ls, show, diff, merge-preview, request-changes and version-diff also take --json. The REST routes are under /v1/projects/<project_id>/change-requests in the API reference.
Errors
| Code | Status | Meaning |
|---|---|---|
CR_HEAD_NOT_AHEAD |
422 |
The head has no commits ahead of the base. Commit your work on the head branch and push it. |
CR_BASE_NOT_SESSION_BASE |
400 |
An agent tried to target a branch other than its session’s base. |
MANIFEST_INVALID |
422 |
The manifest on the head branch fails validation. The body lists the issues. Run kortix validate. |
| (conflict) | 409 |
The merge conflicts. The body lists the files. |
| (not open) | 409 |
The change request is merged or closed. |
CR_SELF_MERGE_REFUSED |
403 |
A session tried to merge its own change request without a merge grant. |
CR_GOVERNANCE_UNVERIFIED |
503 |
Kortix could not read the manifest to check an agent’s merge. Nothing merged. Retry. |
| (manifest unreadable) | 502 |
Kortix could not read the manifest from the head branch. |
The Review page
The Review page is the project’s inbox for everything that waits on a person. Open Review in the project sidebar, or go to /projects/<project_id>/review.
| Item | Id | Comes from |
|---|---|---|
| Change | cr:<id> |
A change request. |
| Approval | call:<id> |
A connector call that a policy holds for approval. See Connectors. |
Output (output), question (decision), finished batch (batch) |
its own id | An agent submits it with kortix review submit. |
The lists are Needs you, Waiting and Done. The filters are All, Changes, Approvals, Outputs, Questions and Finished. Keys: j and k move, Enter opens, a approves, e asks for changes, d dismisses, 1 2 3 switch lists, / searches, ? shows help.
What a verdict does depends on the item:
| Item | approve |
reject |
changes |
|---|---|---|---|
| Change | Merges the change request. | Closes it. | Sends the note to the agent that opened it. A note is required. |
| Approval | Lets the call run. | Denies it. | Not available. |
An item an agent submitted takes every verdict: approve, reject, changes, answer and dismiss.
| Command | Does |
|---|---|
kortix review ls [--segment <s>] [--kind <k>] |
Lists items. Segments: needs_you, waiting, done. Kinds: change, approval, output, decision, batch. |
kortix review show <item> |
Shows one item, with a connector call’s arguments. |
kortix review act <item> <verdict> [--message <t>] |
Decides one item. |
kortix review bulk <verdict> <id>… |
Decides several agent items. It skips changes and approvals, which each need their own review. |
kortix review submit --kind <k> --title <t> |
Submits an output, decision or batch. Options: --summary, --risk (none, low, medium, high), --detail <json>, --agent, --session. |
Reading needs project.review.read, a verdict needs project.review.act, and submit needs project.review.submit. A Member holds read and submit. A Manager holds all three.
Config comes from the base branch
A session runs the current config release of its base branch: kortix.yaml, agents, skills, tools and harness files. It does not run the files in /workspace.
- An edit in
/workspaceor on a session branch takes effect only when it lands on the base branch, through a merge or a push. - Each commit on the base branch is a new config release. A running session converges to it at the start of its next turn. A push or a merge also starts the convergence in the background.
- To apply it at once, select Reload config in the session menu, or run
kortix sessions reload <session_id>. It restarts the agent runtime.--forcereloads during a turn and ends that turn; it asks first unless you pass--yes.--no-reposkips the git pull in/workspace.--statusonly reports whether the session is behind. - To test config before a merge, create a session with
base_refset to your branch. Its change requests target that branch too.
When a release fails to load, the session runs the last release that worked, or else the platform default config. The session header shows Config failed to load. Fix the config on the base branch. See Sandboxes & previews for troubleshooting.
| Limit | Value |
|---|---|
| Config release | 32 MiB compressed, 128 MiB uncompressed |
| Project snapshot | 512 MiB compressed. Above it, running sessions stop picking up config changes until a new session starts. |
kortix validate warning |
One file of 10 MiB or more, or more than 512 MiB in total |
To keep a file in git but out of the release, mark it export-ignore in .gitattributes. A directory rule covers everything under it. The release, the kortix validate size check and session config downloads leave those files out. The manifest file and the OpenCode config directory always ship whole.
fixtures export-ignore
datasets/raw export-ignore
Manage the repository
Customize → Settings → Git repo shows the repository and its Status, the Base branch, the Manifest file, Work on this locally, Use your own Git client and People with access. The base branch is the branch new sessions and change requests start from. From the CLI, kortix projects set --branch <branch> and --manifest <path> change the same fields. Both need project.settings.write.
GitHub connection
GET /v1/projects/<project_id>/git/connection returns the connection state:
state |
Meaning |
|---|---|
connected |
Kortix can reach the repository. |
reconnect_required |
The Kortix GitHub App installation is missing, unusable, or does not match the repository. Open the install_url in the response to install the App again. |
unavailable |
Kortix cannot reach the repository for a reason you cannot fix with the App. |
not_connected |
The project has no repository. |
Replace the repository
Under Git repo, select Change and enter the new GitHub repository URL. Kortix verifies access through the GitHub App. The new repository needs the manifest file on its default branch. Stop active sessions, and close or merge open change requests, first. New sessions use the new repository. Existing sessions cannot restart after the change. The route is PUT /v1/projects/<project_id>/git/repository. It needs project.settings.write and project.connector.write.
Give people direct access
A managed repository lives on GitHub under Kortix. To let someone work on it on github.com, add their GitHub username under People with access, with Can edit or Can view. GitHub emails them an invite. This needs project.members.manage, and works on managed repositories only. For a repository you own, manage access on GitHub.
A repository on another git host
To let Kortix reach a repository on another git host, store a token with PUT /v1/projects/<project_id>/git-credential and the body { "token": "…" }. Kortix keeps it as a platform credential, not as a secret a sandbox can read. It needs project.connector.write. GitHub and managed repositories refuse it with 409. For a repository on another git host, kortix sessions new run in a checkout whose origin is that repository creates the session branch. It pushes your current branch with your own git credentials.
Browse without a clone
kortix files reads the committed repository. It acts on the default branch unless --ref <branch|tag|sha> picks another. Every subcommand needs project.file.read.
| Command | Does |
|---|---|
kortix files ls [<path>] |
Lists files. |
kortix files cat <path> |
Prints a file. |
kortix files search <query> [--content] |
Searches file names, or file contents. |
kortix files history <path> |
Lists the commits that changed a file. |
kortix files branches |
Lists branches, ahead and behind the default branch. |
kortix files commits [--path <p>] |
Lists commits on --ref. |
kortix files show <sha> |
Shows one commit and its files. |
kortix files diff <sha> [--path <p>] |
Prints a commit’s patch. |
kortix files download -o <file.zip> [--path <p>] |
Downloads the repository or a subtree as a zip. |
For uncommitted files in a running sandbox, use kortix sessions cp <session_id>:<path> <dst>. See Sandboxes & previews.
Delete
Deleting a project deletes its managed repository. A repository you connected yourself is left untouched. See Projects.