Reference

Changes from 1.4 to 2.0

What 2.0 changes from 1.4, what 1.4 did, and what to do about each change, plus where the pages moved and what became of your theme.

To update, see Upgrade from 1.4. This page lists what changed.

2.0 changes some 1.4 behavior that the stability policy covers, such as --json fields and exit codes, which is why it is a major release. Apart from the changes listed here, your chains, library and other config files carry over as they are.

Each change below says what is true now, what 1.4 did, and what you do about it. "No action" means nothing to change.

Network access

  • The Host check on a network bind. On a network bind, 2.0 checks the address every browser uses to reach the board. 1.4 skipped that check over plain http, so your board may have worked at a name that isn't in allowed_hosts. Such a browser now gets a page saying the name isn't allowed.
    • Do: before you upgrade, add each exact name you use (no wildcards) on Settings › Access, or to allowed_hosts in access.yaml.
    • kraft admin doctor warns when the bind is off loopback and the list is empty.
  • A tunnel or reverse proxy in front of a loopback-bound Kraft must send a loopback Host. On a server bound to 127.0.0.1 (the default), 2.0 answers 403 to every request whose Host header isn't localhost, 127.0.0.1 or [::1], webhooks to POST /api/triggers and GET /api/health included. 1.4 checked only requests from a browser, and allowed_hosts doesn't apply on a loopback bind. See Who may call for the rule.
    • Do, for the board: bind Kraft beyond loopback, with a password, and list the name in allowed_hosts. That is the one way the board works through cloudflared, ngrok, Caddy or nginx. What a proxy you run must pass is in Behind a reverse proxy you run.
    • Do, for webhooks and scripts alone: have the proxy rewrite Host to localhost. Read Put a proxy in front of Kraft first, because it also changes who has to sign in.

Items and repos

  • A sandbox with no network: is refused. Every agent launch into it stops the item as a configuration error, whatever the harness. 1.4 already refused a Claude task there, and a Codex or Cursor task with a tool policy. It ran every other one, with the worker's network unrestricted and no channel to Kraft, so gates were not enforced. A repo that runs only Claude needs nothing done.
    Do: add a network: policy to the sandbox, or set unrestricted_network: true on it to run as 1.4 did, knowing that. The two keys together are refused when repos.yaml loads. kraft admin doctor fails the egress row of each such repo.
  • Cancel keeps the branch. Cancel in the web interface keeps the item's branch and worktree. 1.4 deleted them. Do: to delete them, run kraft item abandon ID --reason "..." --yes.
  • A merge request closed without merging stops its item. In 1.4 the item kept waiting. Do: reopen the merge request from the item's page, retry, or cancel.
  • A repo with open items can't be disconnected. kraft repo disconnect (and DELETE /api/repos) is refused with a 409 until each of the repo's items is done, cancelled or abandoned, paused and never-started ones included. In 1.4 it went through and left those items stranded. Do: finish, cancel or abandon the items first.
  • test_command: "" means the repo has no tests. Its items pass verify without running a test. In 1.4 such an entry stopped every item at verify. Do: before you upgrade, set a real test command on any such entry, or enabled: false to keep it from running. After the upgrade, the tests row of kraft admin doctor names each such entry.
  • kraft repo connect on a repo that is already connected changes it. In 1.4 it did nothing. Now it probes the repo again and saves what its entry left undecided: a test command where it has none and no test scopes, and a setup command where none is declared.
    • That can enable a repo saved disabled for want of a test command. In a terminal it asks first; with -y, --json or no terminal it saves without asking.
    • Do: a script that re-runs connect to check a repo should run kraft repo list instead.
  • kraft repo connect refuses a repo with no commit yet. 1.4 connected it. Do: commit its files first. kraft admin doctor names each entry 1.4 left without a test or setup command where 2.0 finds one, with the connect command that saves it.

Scripts, JSON output and the API

  • agent_overrides is an object, not JSON text, wherever it appears: in kraft view show --json, in kraft item approve --json and every other --json answer that carries the item, in the MCP tools' results, and in GET /api/work-items/{id}.
    • In the answers to an action, such as kraft item approve --json, node_overrides and policy_override are objects too, as kraft view show --json already had them.
    • Do: if a script runs json.loads on one of them, read it as an object instead.
    • Setting the override works as in 1.4: kraft item set-overrides and PATCH /api/work-items/{id} replace the whole override.
  • budget_cap.cap_usd in kraft view show --json is the cap that stops the item first: the lower of its own cap and its policy's item-wide budget_usd. Do: read budget_cap.key to tell which one it is.
  • kraft view events -f --json prints one JSON object per line from the first line on. 1.4 printed the backlog as an indented array. Do: read it line by line.
  • Action commands print one line. kraft item approve, reject, pause, resume, retry, raise-budget, skip, complete, cancel, escalate and set-chain, kraft item resolve and reopen (one line naming the thread), and kraft item review approve or request-changes at a pending gate, print what they did, not the item's fields (or, for a thread, its comments). Do: a script that read those fields should use --json, which is unchanged.
  • kraft admin restart with no server running exits 1. 1.4 exited 0. update --restart is unchanged. Do: a script that runs it under set -e stops there, so start the server first.
  • kraft admin restart and update --restart list the running items first. In a terminal they ask before going on (-y skips the question); a script with no terminal only prints the list. 1.4 restarted without asking. No action.
  • A review comment names a file inside the repository, and a suggestion goes on new-side lines. kraft item comment, the MCP tool add_review_comment and POST /api/work-items/{id}/threads refuse an absolute --file path, or one with .., with a 422, and kraft item comment --suggest with --side old exits 2 (the API answers 422). 1.4 stored both. Do: use a repository-relative path, and --side new for a suggestion.
  • A model override that is not a model id is refused. kraft item set-overrides, kraft item set-node-override, PATCH /api/work-items/{id} and a retry that changes a task's model (task_config.model) answer one, such as --model "my model", with a 422. 1.4 stored any text. One 1.4 already stored now stops its item at the next launch instead of reaching the agent, and the reason names the command that clears it. Do: run that command.
  • Escalating an item a spend cap stopped is refused, and so is raising a cap to what the item has already spent.
    • kraft item escalate (and POST /api/work-items/{id}/escalate) on an item any spend cap stopped, the daily cap included, answers 409 and points to the cap to raise. 1.4 started an escalation that the same cap then stopped.
    • kraft item raise-budget with a cap at or below what the item has spent answers 422. 1.4 saved it and the item stopped again at once.
    • Do: raise the cap above the spend.
  • A work item title is one line of plain text, and not blank. kraft item create, the MCP tool create_work_item, POST /api/triggers and PATCH /api/work-items/{id} refuse a title that is empty, only whitespace, holds a line break (a trailing one included), or holds a tab, an escape or another control character, with a 422. 1.4 filed or saved it (only a blank PATCH title was refused).
    • Do: if a webhook or script builds the title from text that can be empty or run over several lines, give it a fallback and send its first line, with the rest in description.
    • A schedule's title (a policy.yaml cron trigger in 1.4), and Duplicate of an item 1.4 filed with one, is cleaned up instead of refused: the first line is the title and the rest goes to the top of the description, a tab becomes a space, other control characters are dropped, and a blank title takes the description's first line (a schedule with neither is skipped).
    • kraft view list shows the first line of a title 1.4 stored with a line break, without its control characters.

Chains are named chain, not template, in commands, tools and the API.

BeforeNowWhat to do
kraft item set-chain --template--chain; --template still worksNothing.
The MCP tool set_chain_templateset_chain, with chain for template; the old tool name no longer existsChange an agent prompt or skill that names set_chain_template.
chain_template in the MCP tool create_work_itemchainUse chain.
chain_template in POST /api/triggerschain; chain_template is still readNothing.
default_chain_template in POST and PATCH /api/reposdefault_chain; default_chain_template is still readNothing.
default_chain_template in the answers of kraft repo list --json, kraft repo connect --json, the MCP tool ensure_repo and GET /api/reposdefault_chainRead default_chain in a script that uses the key.

Config directory and files

  • The config directory. The config directory is $KRAFT_HOME/config/, not templates/. Do: nothing for a default home; the first start renames it. The rest of this item says what that touches.
    • The first start renames yours, every file in it as it was, once it holds the server's lock. Started while the 1.4 server is still up, it exits "already running" and touches nothing. templates is left as a link to config, so a 1.4 process still running (an MCP server under an agent) and a path in a script keep working.
    • KRAFT_CONFIG_DIR names it. KRAFT_TEMPLATES_DIR is still read when the new one is unset, and one pointing at the old default is renamed with it, so a service unit or a shell written for 1.4 keeps working. Change it when you next touch that unit.
    • A config/ you made before the first start, such as the harness guide's config/harnesses, gets what it lacks moved in (an empty directory in templates/ is removed). Whatever both hold stays in templates/, and kraft admin doctor's config row names both until you merge them.
    • If a service unit or a shell sets KRAFT_TEMPLATES_DIR to the old default and you also made a config/, 2.0 refuses to start until you merge templates/ into config/ and remove it.
    • A directory you point at anywhere else is never renamed. A templates that is a link to another directory (a dotfiles repo) is not renamed or merged either, and its files are not read, except a harnesses/ directory in it, which stays read until a config/harnesses exists. The start prints a warning naming the target, and the config row warns the same. Do: set KRAFT_CONFIG_DIR to that target.
    • kraft admin doctor --json names the row config, not templates.
  • A schedule's day of week 7 is Sunday, as in cron. 1.4 accepted 7 and never fired it. Ranges (1-5) and steps (*/15) work since 2.0. Do: a 0 9 * * 7 trigger that never ran in 1.4 fires every Sunday after the upgrade, so change or remove it first if that is not what you want.
  • A steering/ directory is no longer read. 0.x read a directory of .md files, and 1.x folded it into library.yaml at the first start. Every 1.x start already folded it, so a directory left over is only files. Do: on a 0.x home updated straight to 2.0, copy those files' text into library.yaml's steering: by hand, because 2.0 loses them as steering (Upgrade from 0.x has the update).
  • Agent profiles in harnesses.yaml are written as providers: (per provider, {model, effort}), the shape Settings › Harnesses edits. The older model: map with one effort still loads. No action.

Three keys moved. Every old place is still read, so a hand-written file stops nothing, except max_concurrent in intake.yaml: the server ignores it, with a warning, until the next start moves it to policy.yaml. kraft admin doctor's moved keys row names each old place you still have.

BeforeNowWhat to do
triggers: in policy.yamlschedules: in intake.yaml, edited on Settings › Auto-intakeNothing: the first start moves it.
max_concurrent in intake.yamlmax_concurrent in policy.yaml, on Settings › Policy › HousekeepingNothing: the first start moves it, unless policy.yaml already sets one.
default_chain_template in a repo entrydefault_chainNothing: the old key is still read.
board.group_by: template in theme.yamlboard.group_by: chainNothing: the old value reads as chain.

Unless the notes below apply, there is nothing to do:

  • The first start carries the first two, in the renamed home or in one you point at by hand: policy.yaml's triggers: become schedules: in intake.yaml, and an intake.yaml max_concurrent moves to policy.yaml unless it already sets one.
  • Each file a key moves between is rewritten, readable by you alone (0600); the rest of it keeps its comments and layout.
  • A moved trigger takes its comments with it: the lines above it and the ones on its own lines. A comment line above triggers: itself stays in policy.yaml, even once every trigger has moved, when it sits over the next key: delete it by hand.
  • A comment on the max_concurrent line is dropped. If keeping a file's comments would leave it unreadable, the start writes it without them, and its log says so.
  • A trigger 2.0's schedule refuses (1.4 ignored an extra key such as enabled: false) stays in policy.yaml, still read and fired, and the rest move. Doctor's moved keys row names the key that keeps each one there (the start's log names only the first).
  • A trigger whose cron names a value outside its field (0 24 * * *, 0 9 * * 8), which 1.4 loaded and never ran, or a number in digits other than 0-9 (², which 1.4 never ran, or ٣, which it ran as 3), stays in policy.yaml too and is skipped, with a warning, rather than refusing the file.

Four repos.yaml keys an older Kraft wrote are not read. A repo still carrying one loads, the server log names it once, and kraft admin doctor's keys row warns, naming what replaced it. gitlab_project is still read as forge: gitlab and project:.

BeforeNowWhat to do
default_modelA model per harness profile under models:Replace the key.
submodulesEach submodule as an entry of its ownReplace the key.
default_root_merge_policyA workspace's root_pointer_defaultReplace the key.
allow_cross_repoNothingRemove it.

Permission hooks

  • Cursor's hook entry runs Kraft's Python with -I. 1.4 wrote the entry in a worktree's .cursor/hooks.json without -I. The server adds -I to each existing worktree's entry when it starts, so a worktree that already exists gets it too. No action. See Permission gate.

The interface

  • 2.0 replaces the interface, and some pages moved. Old addresses still open the right page. No action: see Where the pages moved for where each one went.
  • The theme file changes once. The first start rewrites theme.yaml so the look you had stays. No action: see Your theme if it looks wrong.
  • The phone layout shows on a screen narrower than 768 pixels, with the same pages behind a bottom bar. No action: see Kraft on a phone.
  • The 2.0 VS Code extension needs Kraft 2.0 or a later 2.x. VS Code updates the extension on its own. Against a 1.4 server it shows the board but disables every action, and a 1.4 extension is read-only against a 2.0 server. Do: update Kraft and the extension together; the messages are in VS Code.

Where the pages moved

Kraft 2.0 replaced the interface. The old pages moved, and the old addresses still open the right page.

BeforeNow
Settings › Chains, and the old Templates pageTemplates › Chains (/templates/chains)
Settings › Library and SteeringTemplates › Library (/templates/library); steering profiles are library components
Settings › Repos and Settings › HarnessesStill under Settings (/settings/repos, /settings/harnesses); the /templates/... addresses the 2.0 release candidates gave them open the same pages
Settings › Plugins (/settings/plugins, a redirect to Settings › Chains already in 1.4)Templates › Chains
Settings › Notifications (/settings/notify)Settings › Notifications (/settings/notifications)
Settings › Auto-intake (/settings/intake)Settings › Auto-intake (/settings/auto-intake)
Settings (the front page)Settings › Policy (/settings/policy)
The Search page (/search)The board, with the search box on ⌘K (Ctrl+K) from any page

Repos, Harnesses, Policy, Access and Appearance stayed under Settings. About is still at /settings/about: on the desktop it opens from the version line at the foot of the sidebar, and on a phone from More.

A bookmark to an old address opens the page that replaced it, and keeps its query string. Two older kinds of address:

  • An address from the preview of the new interface, which began with /ng, drops that prefix and opens the same page.
  • A bookmark to an item's old tab address (/work-items/ID#tab=changes) opens the item's review page, and one to a node (#node=NAME) opens that node.

The Current UI link and the Try the new UI banner are gone: there is one interface. On a phone, a screen with no desktop page of its own opens the nearest one: see Kraft on a phone.

Your theme

The 2.0 interface chooses color with surface, accent and colour_amount, not palette. The first start after the upgrade rewrites theme.yaml once, so the look you had stays: your old palette is replaced by the three keys that draw it, and the original file is kept as theme.yaml.pre-2.0 beside it. A home a 1.5.0 release candidate already converted keeps that candidate's copy as its one copy, and gets no second one: theme.yaml.pre-1.5, or theme.yaml.pre-ux2 from 1.5.0rc5 to rc9.

Which surface and accent each old palette becomes, and what happens to a palette that reappears, is in palette is legacy.

If it still looks wrong:

  • Open Settings › Appearance and pick the surface, accent and amount of color you want. That is the supported way to change them.
  • To go back to the file as it was before the upgrade, stop Kraft, copy ~/.kraft/config/theme.yaml.pre-2.0 (or the candidate's copy named above) over theme.yaml, and start it again. The next start converts its palette once more. The saved copy is never overwritten.
  • A theme.yaml that does not parse, or names a value Kraft does not know, is left as it was and the interface falls back to its defaults. kraft admin doctor has no row for this file: fix the key by hand (theme.yaml lists them), or delete the file to start from the defaults.
  • mode: system follows your operating system's light or dark setting.
Copyright © 2026