[{"data":1,"prerenderedAt":65},["ShallowReactive",2],{"$f33nwdv9qh8n5w":3},{"href":4,"title":5,"description":6,"kind":7,"mark":7,"planned":8,"contributors":9,"provenance":7,"html":10,"headings":11},"\u002Fdocs\u002Ftools\u002Fworkspaces","Workspaces","A throwaway clone of one git repository for one run: set up by the repository itself, changed through the workspace tools, pushed to a branch, and deleted when the run ends.",null,false,[],"\u003Ch2 id=\"what-a-workspace-is\">What a workspace is\u003C\u002Fh2>\n\u003Cp>A workspace is a clone of one git repository, made for one run and deleted when that run ends. A member reads,\nchanges, tests and commits code in it, and pushes the result to a branch. Nothing in it outlives the run except\nwhat was pushed.\u003C\u002Fp>\n\u003Cp>A run gets a workspace only when its task declares workspace tools: \u003Ccode>workspace.read_file\u003C\u002Fcode>, \u003Ccode>workspace.edit\u003C\u002Fcode>,\n\u003Ccode>workspace.run_command\u003C\u002Fcode>, \u003Ccode>workspace.git_push\u003C\u002Fcode> and the rest, each listed by name like any other tool. A task\nwithout them never clones anything. What each tool takes and returns is in the\n\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in\u002Fworkspace\">workspace tools reference\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>Everything a workspace does is plain git and the repository's own tooling: it clones, branches, commits, merges and\npushes through the repository's remote, and sets up and checks the code with the commands the repository declares\n(see \u003Ca href=\"#what-the-repository-provides\">What the repository provides\u003C\u002Fa>). None of that depends on where the repository\nis hosted: the remote's address says which host it is, and the credential for it comes from the code host tool you\nbind. One thing still reaches a code host's API of its own: a branch named after an issue. See\n\u003Ca href=\"#where-the-repository-lives\">Where the repository lives\u003C\u002Fa> and \u003Ca href=\"#the-branch\">The branch\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>The workspace tools only touch the clone. Issues, pull requests, reviews and merging happen on your code host,\nthrough the host's own MCP server, bound like any other tool: \u003Ca href=\"\u002Fdocs\u002Ftools\u002Fgithub\">GitHub\u003C\u002Fa> has a setup guide, and\nany other host's server can be bound from \u003Ca href=\"\u002Fdocs\u002Ftools\">Tools\u003C\u002Fa>.\u003C\u002Fp>\n\u003Ch2 id=\"where-the-repository-lives\">Where the repository lives\u003C\u002Fh2>\n\u003Cp>A run names its repository by its git remote address, on any host that serves git: GitLab, Bitbucket, Azure\nRepos, a self-hosted server, or GitHub.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{ &quot;repo&quot;: &quot;https:\u002F\u002Fgit.example.com\u002Facme\u002Fwebsite.git&quot; }\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Nothing about the host is set in the OS. The workspace reads what it needs from the remote itself: the host from\nthe address, and the default branch and the branches from git.\u003C\u002Fp>\n\u003Cp>The repository is cloned the first time the run needs it, with its last 50 commits.\u003C\u002Fp>\n\u003Ch3 id=\"the-credential-comes-from-the-code-host-tool-you-bind\">The credential comes from the code host tool you bind\u003C\u002Fh3>\n\u003Cp>The clone and every push use the code host tool your enterprise or team binds on \u003Ca href=\"\u002Fdocs\u002Ftools\">Tools\u003C\u002Fa>. A workspace\nreads these custom props off the binding: key → value settings added behind \u003Cstrong>Custom props\u003C\u002Fstrong> on its form.\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Key\u003C\u002Fth>\n\u003Cth>What it is\u003C\u002Fth>\n\u003Cth>GitHub's\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>git.host\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The host this binding's token is valid for\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>github.com\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>git.username\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The username git sends alongside the token\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>x-access-token\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>git.repoTemplate\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A clone address with \u003Ccode>{repo}\u003C\u002Fcode> in it, applied to a short \u003Ccode>owner\u002Fname\u003C\u002Fcode> input\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>https:\u002F\u002Fgithub.com\u002F{repo}.git\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>git.repo\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The repository the binding's layer works in, for a run whose input names none\u003C\u002Ftd>\n\u003Ctd>Usually left out\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>The workspace takes the binding, among those the run reaches (task, role, member, team, enterprise, in that\norder), whose \u003Ccode>git.host\u003C\u002Fcode> matches the remote's. Each tool counts once: the nearest binding of it the run reaches, so\na member's own binding without the props hides the enterprise's. Its token is given to git scoped to that host, and only over\n\u003Ccode>https:\u002F\u002F\u003C\u002Fcode>, so a prompt from any other host is answered with nothing. A remote on a host no binding names is cloned\nwith no credential: a public repository still clones, and nothing pushes. The \u003Ca href=\"\u002Fdocs\u002Ftools\u002Fgithub\">GitHub\u003C\u002Fa> guide\nsays what to set for GitHub.\u003C\u002Fp>\n\u003Cp>A binding may also name the repository its layer works in (\u003Ccode>git.repo\u003C\u002Fcode>), and then a run needs no \u003Ccode>repo\u003C\u002Fcode> input at\nall; a run that gives one overrides it. Because a binding can carry \u003Ccode>git.repoTemplate\u003C\u002Fcode>, a short input keeps working:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{ &quot;repo&quot;: &quot;acme\u002Fwebsite&quot; }\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>A run with no repository — neither in its input nor on a binding it reaches — fails \u003Ccode>workspace_repo_missing\u003C\u002Fcode>\nbefore it calls anything. A run that names a short \u003Ccode>owner\u002Fname\u003C\u002Fcode> no binding's \u003Ccode>git.repoTemplate\u003C\u002Fcode> expands fails\n\u003Ccode>workspace_repo_no_clone_address\u003C\u002Fcode> when it opens its workspace: the OS never guesses a host for it. Tools says when\ntasks work in a workspace and no binding has \u003Ccode>git.*\u003C\u002Fcode> props, and flags a binding without them when another binding\nof the same tool has them.\u003C\u002Fp>\n\u003Ch2 id=\"the-branch\">The branch\u003C\u002Fh2>\n\u003Cp>A run works on one branch, chosen in this order:\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>The run works on\u003C\u002Fth>\n\u003Cth>When\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Its \u003Ccode>branch\u003C\u002Fcode> input\u003C\u002Ftd>\n\u003Ctd>The run was started with one, or the task before it handed one on.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>The task's branch template\u003C\u002Ftd>\n\u003Ctd>The task's workspace sets \u003Ccode>branch\u003C\u002Fcode>. Each \u003Ccode>{key}\u003C\u002Fcode> in it is filled from the run's input: \u003Ccode>fix\u002F{issue}\u003C\u002Fcode> with \u003Ccode>{ &quot;issue&quot;: 42 }\u003C\u002Fcode> is \u003Ccode>fix\u002F42\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os\u002F&lt;task slug&gt;-&lt;first 8 characters of the run id&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Neither. A new branch.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>A template with \u003Ccode>{issue_slug}\u003C\u002Fcode> in it is worked out once, from GitHub. When the run's input names a pull request\n(\u003Ccode>number\u003C\u002Fcode>) and the task can read pull requests, the run takes that pull request's own branch. Otherwise it names\nthe branch after the issue in the input (\u003Ccode>issue\u003C\u002Fcode>): \u003Ccode>os\u002F&lt;issue number&gt;-&lt;the issue's title as a slug&gt;\u003C\u002Fcode>. The name is\nstored as the run's \u003Ccode>branch\u003C\u002Fcode> input and every task after it inherits it, so renaming the issue later never moves\nthe branch. The task has to be able to read the issue or pull request (\u003Ccode>github.issue_read\u003C\u002Fcode>,\n\u003Ccode>github.pull_request_read\u003C\u002Fcode>, or all of GitHub); if the branch cannot be worked out, the run fails\n\u003Ccode>workspace_branch_unresolved\u003C\u002Fcode> before it starts work.\u003C\u002Fp>\n\u003Cp>A branch that exists on the remote is checked out as it is there. One that does not is created from the default\nbranch: the branch the clone checks out, which is the repository's own default.\u003C\u002Fp>\n\u003Ch3 id=\"kept-up-with-the-default-branch\">Kept up with the default branch\u003C\u002Fh3>\n\u003Cp>When the run's branch already exists and is behind the default branch, the default branch is merged into it and\npushed before setup runs and before the member does anything, so an open pull request never falls more than one\nrun behind. It is an ordinary merge and an ordinary push. If the merge conflicts, or the remote refuses the push, it\nis undone and the branch is left exactly as the remote has it, for the run to deal with.\u003C\u002Fp>\n\u003Ch2 id=\"what-the-repository-provides\">What the repository provides\u003C\u002Fh2>\n\u003Cp>How the code is set up and checked belongs to the repository, not to the task. Tasks are shared between\nenterprises through the catalog and pointed at other repositories, so a task's skill never names one repository's\nscripts.\u003C\u002Fp>\n\u003Ch3 id=\"setup\">Setup\u003C\u002Fh3>\n\u003Cp>After cloning, the workspace runs the repository's setup. With no setup file, it works out what to run, and runs\nboth lines when both apply:\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>The repository has\u003C\u002Fth>\n\u003Cth>Setup runs\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>mise.toml\u003C\u002Fcode> or \u003Ccode>.tool-versions\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>mise install\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>package.json\u003C\u002Fcode> with a \u003Ccode>packageManager\u003C\u002Fcode> of npm, and no \u003Ccode>pnpm-lock.yaml\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>npm ci\u003C\u002Fcode>, or \u003Ccode>npm install\u003C\u002Fcode> without \u003Ccode>package-lock.json\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Any other \u003Ccode>package.json\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>pnpm install --frozen-lockfile\u003C\u002Fcode>, or \u003Ccode>pnpm install\u003C\u002Fcode> without \u003Ccode>pnpm-lock.yaml\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>To choose for yourself, add \u003Ccode>.os\u002Fworkspace.yml\u003C\u002Fcode> (or \u003Ccode>.os\u002Fworkspace.yaml\u003C\u002Fcode>, or \u003Ccode>.os\u002Fworkspace.json\u003C\u002Fcode>) at the root of\nthe repository:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yaml\"># Leave setup out to use the list above. `setup: []` skips setup altogether.\nsetup:\n  - [pnpm, install, --frozen-lockfile]\n  - [pnpm, run, generate]\nteardown:\n  - [node, scripts\u002Fclean-up.mjs]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n\u003Cli>Each step is one line, \u003Ccode>- [binary, argument, …]\u003C\u002Fcode>: no shell, and no other YAML form. In this form an argument\ncannot contain a comma.\u003C\u002Fli>\n\u003Cli>The binary is one of \u003Ccode>git\u003C\u002Fcode>, \u003Ccode>node\u003C\u002Fcode>, \u003Ccode>npm\u003C\u002Fcode>, \u003Ccode>pnpm\u003C\u002Fcode> and \u003Ccode>mise\u003C\u002Fcode>. Anything else a repository needs (another\nlanguage, a particular version of Node) comes through mise: declare it in \u003Ccode>mise.toml\u003C\u002Fcode>, and \u003Ccode>mise install\u003C\u002Fcode>\ninstalls it.\u003C\u002Fli>\n\u003Cli>\u003Ccode>setup\u003C\u002Fcode> runs in order after every clone. If a step exits non-zero, the clone is thrown away and the workspace\ncall fails with \u003Ccode>workspace_setup_failed\u003C\u002Fcode>, naming the binary and the start of its output. The member is handed\nthe error, and its next workspace call tries again.\u003C\u002Fli>\n\u003Cli>\u003Ccode>teardown\u003C\u002Fcode> runs when the workspace is deleted. A step that fails there is ignored.\u003C\u002Fli>\n\u003Cli>The JSON form is the same two lists: \u003Ccode>{ &quot;setup&quot;: [[&quot;pnpm&quot;, &quot;install&quot;]], &quot;teardown&quot;: [] }\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"git-hooks\">Git hooks\u003C\u002Fh3>\n\u003Cp>The repository's own git hooks run on the commits and pushes a member makes, once setup has installed them (a\n\u003Ccode>prepare\u003C\u002Fcode> script that installs hooks runs as part of \u003Ccode>pnpm install\u003C\u002Fcode> or \u003Ccode>npm ci\u003C\u002Fcode>). The workspace tools never skip\nthem, and refuse the usual ways around them. A hook that fails refuses the commit or the push, and its output goes\nback to the member to fix.\u003C\u002Fp>\n\u003Ch3 id=\"commands-for-the-engineering-tasks\">Commands for the engineering tasks\u003C\u002Fh3>\n\u003Cp>The engineering tasks in the catalog (writing an issue's tests, implementing it, validating it) never run a\nrepository's scripts by name. They call three \u003Ca href=\"https:\u002F\u002Fmise.jdx.dev\">mise\u003C\u002Fa> tasks, and the repository's\n\u003Ccode>mise.toml\u003C\u002Fcode> says what each one does:\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Command\u003C\u002Fth>\n\u003Cth>Called as\u003C\u002Fth>\n\u003Cth>What it has to do\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os:red\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>mise run os:red &lt;issue&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Run the issue's tests: the test files the branch adds or changes. Print each file's path. Exit 0 when every test passes, 3 when the branch has no tests to run, anything else when a test fails. Its mise \u003Ccode>description\u003C\u002Fcode> says where this repository keeps tests and which framework runs them: the task that writes tests reads it.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os:check\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>mise run os:check [issue]\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Everything to run once before committing: lint, typecheck, tests, and whatever build they need first. Exit 0 when it all passes.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os:coverage\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>mise run os:coverage [issue]\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Measure test coverage of the lines the branch changed, against the repository's own thresholds. Exit 0 when they are met; otherwise list the uncovered lines.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>\u003Ccode>os:check\u003C\u002Fcode> and \u003Ccode>os:coverage\u003C\u002Fcode> may also exit 6 when every failure is a test that fails on the default branch too,\nprinting each as \u003Ccode>pre_existing_failure: &lt;file&gt; › &lt;test&gt;\u003C\u002Fcode>. The tasks then treat the default branch as broken rather\nthan the pull request, and look again later.\u003C\u002Fp>\n\u003Cp>A repository with no \u003Ccode>os:red\u003C\u002Fcode> stops those tasks with a message saying it needs one. Any task can run whatever else\nthe repository's \u003Ccode>mise.toml\u003C\u002Fcode> defines, through \u003Ccode>workspace.run_command\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch3 id=\"a-check-has-to-fit-one-command\">A check has to fit one command\u003C\u002Fh3>\n\u003Cp>Every command a member runs has \u003Ca href=\"#limits\">limits\u003C\u002Fa>: ten minutes, and 32,000 characters of output. A check that runs\npast the first is stopped before it can say what failed, and one that prints past the second is cut short. So:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Print what failed, not everything.\u003C\u002Fstrong> Keep the full output in a file in the clone and print where it is. A member\ncan read the file when it needs to.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Split a check that cannot finish in ten minutes.\u003C\u002Fstrong> \u003Ccode>mise run os:check --parts\u003C\u002Fcode> prints the name of each part, one\nper line, and \u003Ccode>mise run os:check --part &lt;name&gt;\u003C\u002Fcode> runs that part over the whole repository, with the same exit codes.\nA task that checks a whole branch asks for the parts and runs each in turn. A repository that does not answer\n\u003Ccode>--parts\u003C\u002Fcode> is checked with one \u003Ccode>mise run os:check\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"commands-for-a-dependency-refresh\">Commands for a dependency refresh\u003C\u002Fh3>\n\u003Cp>Tasks that keep a repository's dependencies current call three more mise tasks. They are optional: a repository\nwithout them is not offered a refresh. Which package manager it uses, what counts as out of date and how a version\nis changed are the repository's own. The tasks name no package manager, so the same tasks work on any repository\nthat answers these commands, and a repository can cover container images or CI actions by changing its own script.\u003C\u002Fp>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Command\u003C\u002Fth>\n\u003Cth>Called as\u003C\u002Fth>\n\u003Cth>What it has to do\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os:deps\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>mise run os:deps\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Print what is out of date, in the form below. Exit 0 when the report is complete, with or without updates. Exit non-zero when the scan could not be completed: never print an empty report for a scan that failed.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os:deps:apply\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>the lines \u003Ccode>os:deps\u003C\u002Fcode> printed\u003C\u002Ftd>\n\u003Ctd>Move each named dependency to exactly the version named, and update the lockfile. Skip one that is already there, so a line can be run again. Exit non-zero, changing nothing, when an argument matches nothing the repository declares.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>os:deps:verify\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>mise run os:deps:verify\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Exit non-zero, naming each one, when a dependency now resolves lower than on the commit the branch started from.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>The report is markdown, because a task copies parts of it into issues as they are:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-markdown\">updates: 2 security: 1 majors: 1 held: 1\n\n## Updates to apply\n\n| Package | Current | Target | Level | Security | Note |\n| --- | --- | --- | --- | --- | --- |\n| `left` | 1.0.0 | 1.0.1 | patch | high | |\n| `right` | 2.1.0 | 2.2.0 | minor | | |\n\nApply in this order, running the checks after each group:\n\n1. Security: `mise run os:deps:apply left@1.0.1`\n2. Minor: `mise run os:deps:apply right@2.2.0`\n\n## Majors\n\n### Major upgrade: framework 3 to 4\n\n- `framework` 3.2.0 to 4.1.0 (released 2026-08-25)\n- Release notes: https:\u002F\u002Fexample.com\u002Fframework\u002Freleases\n- Apply: `mise run os:deps:apply --major framework@4.1.0`\n\n## Held back\n\n- `odd` 1.0.0: the registry names no version to compare\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>What the tasks rely on:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>The first line\u003C\u002Fstrong> is the counts. \u003Ccode>updates\u003C\u002Fcode> and \u003Ccode>majors\u003C\u002Fcode> both 0 means there is nothing to do.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>## Updates to apply\u003C\u002Fcode>\u003C\u002Fstrong> holds the changes that are safe to make together: a table, then one runnable\n\u003Ccode>mise run os:deps:apply ...\u003C\u002Fcode> line for each group, numbered in the order to apply them. These become one issue,\nand one task runs the lines exactly as printed. What an argument looks like is the repository's choice.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>## Majors\u003C\u002Fcode>\u003C\u002Fstrong> has one \u003Ccode>### \u003C\u002Fcode> heading for each upgrade that may break the code, in the order to take them. The\nheading is used as the issue's title, so it must read the same on every scan for as long as that upgrade is on\noffer. A person rejects an upgrade by closing its issue as not planned, and a title that is already on an issue,\nopen or closed, is never filed again. Under the heading: what moves, where to read what changed, and its own\napply line.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>\u003Ccode>## Held back\u003C\u002Fcode>\u003C\u002Fstrong> names anything the scan could not decide, with the reason. Nothing is done with these.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>The whole report\u003C\u002Fstrong> fits in 32,000 characters. When it would not, leave out the least urgent updates and say how\nmany were left out.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Deciding what is safe is the repository's job too. Things worth deciding there: how old a release must be before it\nis offered, whether a fix for a known vulnerability waits that long, and never offering a prerelease or a withdrawn\nrelease.\u003C\u002Fp>\n\u003Ch2 id=\"what-a-task-decides\">What a task decides\u003C\u002Fh2>\n\u003Cp>A task version's \u003Cstrong>Workspace\u003C\u002Fstrong> decides which branch its runs work on and which files they may change. On a task\nin the portal it is \u003Cstrong>Workspace JSON\u003C\u002Fstrong>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;branch&quot;: &quot;fix\u002F{issue}&quot;,\n  &quot;writeAllow&quot;: [&quot;src\u002F**&quot;, &quot;tests\u002F**&quot;],\n  &quot;writeDeny&quot;: [&quot;src\u002Fgenerated\u002F**&quot;]\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Field\u003C\u002Fth>\n\u003Cth>What it does\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>branch\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The branch template (see \u003Ca href=\"#the-branch\">The branch\u003C\u002Fa>).\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>writeAllow\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Paths the run may change freely. Once it is set, every path it does not match is refused, unless \u003Ccode>appendOnly\u003C\u002Fcode> matches it.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>rewriteAllow\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Paths the run may change freely, without refusing everything else.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>writeDeny\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Paths the run may never change. It wins over every other field.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>appendOnly\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Paths the run may add files and lines to, but where no line that was there when the run started may change or go.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>requireFiles\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Paths that must exist in the clone before the run starts work; each \u003Ccode>{key}\u003C\u002Fcode> is filled from the run's input. If one is missing, the run fails \u003Ccode>workspace_file_missing\u003C\u002Fcode>.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Paths are globs: \u003Ccode>*\u003C\u002Fcode> stays inside one folder, \u003Ccode>**\u003C\u002Fcode> crosses folders. A task with none of the path fields set may\nchange anything in the clone.\u003C\u002Fp>\n\u003Cp>The rules are checked when a file is written, edited or deleted, again on what a commit would include (a command\ncan change files too), and, for \u003Ccode>appendOnly\u003C\u002Fcode>, once more on the run's own commits when it pushes. A refused write\nchanges nothing; a refused commit commits nothing. During a merge, a file the merge brings in may be written and\ncommitted as the merge has it, whatever the rules say, so a run can finish a merge; a line of the run's own in\nsuch a file is still refused.\u003C\u002Fp>\n\u003Ch2 id=\"commits-and-pushes\">Commits and pushes\u003C\u002Fh2>\n\u003Cp>Commits are authored and committed as \u003Cstrong>the member the run belongs to\u003C\u002Fstrong>: its name and the email address on its\nmember page. A run with no member commits as the enterprise itself, under your enterprise's name, from an \u003Ccode>os@\u003C\u002Fcode>\naddress on your enterprise's Zero Human mail domain.\u003C\u002Fp>\n\u003Cp>Code hosts link a commit to an account by its email address. For commits to show under the member's own account\non your host, add the member's address to that account and verify it (for GitHub, see\n\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fgithub\">GitHub\u003C\u002Fa>). The push itself is made by whichever account the credential belongs to.\u003C\u002Fp>\n\u003Cp>\u003Ccode>workspace.git_commit\u003C\u002Fcode> stages what the run changed and commits it; a new file that only a command created may need\na \u003Ccode>git add\u003C\u002Fcode> first. \u003Ccode>workspace.git_push\u003C\u002Fcode> pushes the branch to the repository's remote under the same name, as an\nordinary push: it never overwrites work someone else pushed.\u003C\u002Fp>\n\u003Cp>If a run of a task that lists \u003Ccode>workspace.git_push\u003C\u002Fcode> finishes without a successful push while its workspace holds\nchanges or commits the remote does not have, it fails: \u003Ccode>git_push_missing\u003C\u002Fcode>, or the reason the commit or push was\nrefused. Work is not lost behind a success. If a run runs out of steps, or its model call fails, the OS commits and\npushes what it had before the run fails, where the task's rules allow, so a retry picks up from there.\u003C\u002Fp>\n\u003Ch2 id=\"what-is-never-allowed\">What is never allowed\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>\u003Cstrong>Pushing the default branch.\u003C\u002Fstrong> \u003Ccode>workspace.git_push\u003C\u002Fcode> refuses the repository's default branch, and any branch\nnamed \u003Ccode>main\u003C\u002Fcode> or \u003Ccode>master\u003C\u002Fcode> (\u003Ccode>default_branch_refused\u003C\u002Fcode>). A member has no other way to push: \u003Ccode>workspace.run_command\u003C\u002Fcode>\nrefuses a command with \u003Ccode>git push\u003C\u002Fcode> anywhere in it, including through \u003Ccode>mise\u003C\u002Fcode>, \u003Ccode>pnpm\u003C\u002Fcode> or \u003Ccode>node\u003C\u002Fcode>\n(\u003Ccode>git_push_via_run_command\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Skipping the repository's hooks.\u003C\u002Fstrong> A git command with \u003Ccode>--no-verify\u003C\u002Fcode>, \u003Ccode>git commit -n\u003C\u002Fcode>, or a \u003Ccode>core.hooksPath\u003C\u002Fcode>\noverride is refused (\u003Ccode>git_hooks_skip_refused\u003C\u002Fcode>), and the file tools write nothing under \u003Ccode>.git\u002Fhooks\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>A shell.\u003C\u002Fstrong> \u003Ccode>workspace.run_command\u003C\u002Fcode> runs one binary with its arguments: no pipes, no redirects, no \u003Ccode>cd\u003C\u002Fcode>, no\n\u003Ccode>&amp;&amp;\u003C\u002Fcode>. The binary is one of \u003Ccode>git\u003C\u002Fcode>, \u003Ccode>node\u003C\u002Fcode>, \u003Ccode>npm\u003C\u002Fcode>, \u003Ccode>pnpm\u003C\u002Fcode> and \u003Ccode>mise\u003C\u002Fcode>, named, not given as a path\n(\u003Ccode>bin_not_allowlisted\u003C\u002Fcode>).\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Files outside the clone.\u003C\u002Fstrong> Every path the file tools read or write is inside the clone. A path that resolves\noutside it is refused.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"what-waits-for-you\">What waits for you\u003C\u002Fh2>\n\u003Cp>Nothing a run does in its workspace waits for you. The OS answers every \u003Ccode>workspace.*\u003C\u002Fcode> and \u003Ccode>memory.*\u003C\u002Fcode> call before it\nreads the task's gates, so a gate declared on one of those tools is saved but never waits: every write, commit and\npush goes ahead as the task's rules allow, and only ever to a branch that is not the default one. See\n\u003Ca href=\"\u002Fdocs\u002Ftools\u002Fbuilt-in#gates-and-grants\">Gates and grants\u003C\u002Fa> in the built-in tools reference.\u003C\u002Fp>\n\u003Cp>The decision that waits is the merge. Getting a branch into the default branch goes through your code host: a pull\nrequest, or merge request, and the host's merge tool, which a task that merges should gate. On GitHub it has to: a\ntask that names \u003Ccode>github.merge_pull_request\u003C\u002Fcode> cannot be saved without a gate on it. See\n\u003Ca href=\"\u002Fdocs\u002Fcontrol\u002Fgates\">Gates and tool scopes\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cdiv class=\"prose__planned\">\n\u003Cp class=\"prose__flag\">Planned\u003C\u002Fp>\n\u003Cp>What a merge needs will be set on the task's own gate, for any host's merge tool: that the gate is required, that\nthe run writes a summary before it waits, and that approving it ends the run. The OS will not know GitHub's merge\ntool by name.\u003C\u002Fp>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"limits\">Limits\u003C\u002Fh2>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Limit\u003C\u002Fth>\n\u003Cth>Value\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>One command\u003C\u002Ftd>\n\u003Ctd>10 minutes, then it is stopped with everything it started.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>A command's output\u003C\u002Ftd>\n\u003Ctd>At most 32,000 characters go back to the member. The exit code always does.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Setup and teardown\u003C\u002Ftd>\n\u003Ctd>8 steps each, each at most 16 words.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Clone depth\u003C\u002Ftd>\n\u003Ctd>The last 50 commits.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Reading without changing\u003C\u002Ftd>\n\u003Ctd>10 repeated reads (the same file, folder or search again, or an ad hoc command) with no write, edit or commit between them, and a per-run budget of 150 distinct files, folders and searches. The next one past either is refused (\u003Ccode>exploration_budget_exceeded\u003C\u002Fcode>), and the member is told to act on what it has read.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Search\u003C\u002Ftd>\n\u003Ctd>100 matches by default, 500 at most.\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Running \u003Ccode>mise run …\u003C\u002Fcode>, \u003Ccode>git status\u003C\u002Fcode>, \u003Ccode>git diff\u003C\u002Fcode>, \u003Ccode>git log\u003C\u002Fcode> or \u003Ccode>git commit\u003C\u002Fcode> is progress, not reading: it starts the\ncount of repeated reads again. Nothing resets the per-run budget, but a file, folder or search the run has already\nread never counts against it twice.\u003C\u002Fp>\n\u003Ch2 id=\"troubleshooting\">Troubleshooting\u003C\u002Fh2>\n\u003Cdiv class=\"prose__table\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>What you see\u003C\u002Fth>\n\u003Cth>Why\u003C\u002Fth>\n\u003Cth>What to do\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>workspace_setup_failed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A setup step exited non-zero\u003C\u002Ftd>\n\u003Ctd>Run the same step in a fresh clone of the repository and fix it there, or change \u003Ccode>.os\u002Fworkspace.yml\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>workspace_manifest_invalid\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>.os\u002Fworkspace.yml\u003C\u002Fcode> is not in the form above\u003C\u002Ftd>\n\u003Ctd>One step per line, \u003Ccode>- [binary, argument]\u003C\u002Fcode>, under \u003Ccode>setup:\u003C\u002Fcode> or \u003Ccode>teardown:\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>workspace_setup_bin:&lt;name&gt;\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A setup step uses a binary that is not allowed\u003C\u002Ftd>\n\u003Ctd>Run it through \u003Ccode>mise\u003C\u002Fcode>, or a \u003Ccode>package.json\u003C\u002Fcode> script\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>tool_auth_denied:workspace.…\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The host refused the clone or the push\u003C\u002Ftd>\n\u003Ctd>Check that the code host binding's account can write to the repository, and that its token has not expired\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>workspace_repo_missing\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Neither the run's input nor a binding it reaches names a repository\u003C\u002Ftd>\n\u003Ctd>Give the run a \u003Ccode>repo\u003C\u002Fcode>, or set the repository on the code host binding\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>workspace_repo_no_clone_address\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run names a short \u003Ccode>owner\u002Fname\u003C\u002Fcode>, and the nearest code host binding it reaches has no \u003Ccode>git.repoTemplate\u003C\u002Fcode> to expand it\u003C\u002Ftd>\n\u003Ctd>Add \u003Ccode>git.repoTemplate\u003C\u002Fcode> under Custom props on that binding on Tools, or give the run a full \u003Ccode>repo\u003C\u002Fcode> address\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>The clone asks for a password, or the push is refused\u003C\u002Ftd>\n\u003Ctd>No binding the run reaches names the remote's host\u003C\u002Ftd>\n\u003Ctd>Add \u003Ccode>git.host\u003C\u002Fcode> and \u003Ccode>git.username\u003C\u002Fcode> under Custom props on that code host's binding, which holds the token — see \u003Ca href=\"#where-the-repository-lives\">Where the repository lives\u003C\u002Fa>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>default_branch_refused\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run was working on the default branch\u003C\u002Ftd>\n\u003Ctd>Give the task a \u003Ccode>branch\u003C\u002Fcode> template, or start the run with a \u003Ccode>branch\u003C\u002Fcode> input\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>git_push_missing\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run finished with work it never pushed\u003C\u002Ftd>\n\u003Ctd>Read the run's last commit and push calls: a refused commit or push says why\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>workspace_write_not_allowed\u003C\u002Fcode> or \u003Ccode>workspace_commit_not_allowed\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A path outside the task's rules\u003C\u002Ftd>\n\u003Ctd>Change the task's \u003Cstrong>Workspace\u003C\u002Fstrong>, or the work\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>workspace_append_only\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The run changed or removed a line in an add-only path\u003C\u002Ftd>\n\u003Ctd>The run has to add lines, not change them, or the path needs \u003Ccode>writeAllow\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n",[12,16,19,23,26,29,32,35,38,41,44,47,50,53,56,59,62],{"id":13,"text":14,"level":15,"planned":8},"what-a-workspace-is","What a workspace is",2,{"id":17,"text":18,"level":15,"planned":8},"where-the-repository-lives","Where the repository lives",{"id":20,"text":21,"level":22,"planned":8},"the-credential-comes-from-the-code-host-tool-you-bind","The credential comes from the code host tool you bind",3,{"id":24,"text":25,"level":15,"planned":8},"the-branch","The branch",{"id":27,"text":28,"level":22,"planned":8},"kept-up-with-the-default-branch","Kept up with the default branch",{"id":30,"text":31,"level":15,"planned":8},"what-the-repository-provides","What the repository provides",{"id":33,"text":34,"level":22,"planned":8},"setup","Setup",{"id":36,"text":37,"level":22,"planned":8},"git-hooks","Git hooks",{"id":39,"text":40,"level":22,"planned":8},"commands-for-the-engineering-tasks","Commands for the engineering tasks",{"id":42,"text":43,"level":22,"planned":8},"a-check-has-to-fit-one-command","A check has to fit one command",{"id":45,"text":46,"level":22,"planned":8},"commands-for-a-dependency-refresh","Commands for a dependency refresh",{"id":48,"text":49,"level":15,"planned":8},"what-a-task-decides","What a task decides",{"id":51,"text":52,"level":15,"planned":8},"commits-and-pushes","Commits and pushes",{"id":54,"text":55,"level":15,"planned":8},"what-is-never-allowed","What is never allowed",{"id":57,"text":58,"level":15,"planned":8},"what-waits-for-you","What waits for you",{"id":60,"text":61,"level":15,"planned":8},"limits","Limits",{"id":63,"text":64,"level":15,"planned":8},"troubleshooting","Troubleshooting",1791124519838]