You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
A worker's, approved operation's or release command's private temp directory is checked before it is reused: a link or a file left in its place is removed, and a directory owned by someone else is refused, so the sandbox never grants writes through it.
Approved operations, the UI application and release commands get the toolchain profile a check gets (#26). Each built its sandbox from the check profile alone, so .NET there had no NuGet cache, no NIS domain name rule and no -m:1 handling. Measured under srt on macOS (SDK 9.0.305): an approved command and a deploy, each with a fresh private home, died at the SDK's first run on every dotnet command ('NuGet-Migrations', the mutex under /tmp/.dotnet); ui.environment.start_command: [dotnet, run, --project, Web] for a web project referencing two libraries was never ready ("(no output)" after MSBuild's node retries); and built with -m:1 first, its host hung at startup and its HttpClient died on GetDomainName: -1. Now each finds its toolchains from its command and the checkout as a check does: the repository's dependency caches read-only, build state private to the command, NuGet's audit decided by the hosts its sandbox allows, and for .NET a private home prepared as a check's, the NIS rule and IPv4 sockets on macOS, and the runner's early stop when MSBuild records a refused worker node, with a note naming the fix for that command's field. A deploy stopped that way fails (srt exits 0 when stopped, which no longer reads as deployed), a verify command whose build was refused leaves the deploy UNKNOWN, and an application stopped that way ends the UI run at once instead of after ready_timeout_seconds. A release command and an approved operation, which may fetch on the hosts their sandbox allows, write dependency caches of their own (removed after the command), with the repository's read-only beneath them where the tool reads a second cache: NUGET_FALLBACK_PACKAGES, a GOPROXY whose first entry is the repository's module cache, and Gradle's and Maven's read-only layers; Cargo and pip start empty there, as under the private home they had before, so a Rust or Python deploy needs its registry hosts in network_hosts. Each finds the host's rustup installation as a check does, in RUSTUP_HOME of the controller's environment, else in ~/.rustup of the account's home (also when the caller names no home, as the sandbox profile already assumed), so rustup's cargo proxy finds its toolchain under the command's private home: on GitHub's runners a deploy cargo install --path . --offline failed with rustup could not choose a version of cargo to run. With the check's read-only caches alone they could fetch nothing: under srt on macOS a deploy go run . that fetched one module from a proxy in the repository failed with go: writing go.mod cache: mkdir <orbit home>/toolchains/<key>/gomod/cache: operation not permitted, and a NuGet restore from a feed in the repository and cargo install --path . were refused the same way, which would have failed every deploy or verify command needing something the run's install did not fetch (a deploy tool in another toolchain, go run tool@version, a merge commit's new package). An approved command that is itself a package install with a toolchain it uses, such as dotnet add package, is the install for that toolchain and may write the repository's cache of that toolchain only, on the policy's hosts plus the approved one, and its output says whose cache was writable. An approved command Orbit stopped (a refused node, the run's cancellation, the time limit) has no exit code in its receipt, where a cancelled one read exit 0, and a command that exited on its own before Orbit's look no longer reads as stopped. A deploy's and a verify command's temp directory, and the UI run's, are emptied before each use, so a node report an earlier invocation left cannot decide a later one. The application keeps the account's Python user site-packages (no private PYTHONUSERBASE). orbit doctor (checks.sandbox) judges ui.environment.start_command and, in mode release, each release environment's deploy_command and verify_command with the .NET rules (-m:1, dotnet run in two steps, dotnet format), as mandatory, with argv-ready fixes (["sh", "-c", "dotnet build Web -m:1 && dotnet run --project Web --no-build"]). Every .NET process under srt on macOS also gets DOTNET_USE_POLLING_FILE_WATCHER=1 unless it sets the variable: the sandbox denies the FSEvents service a native file watcher asks, and ASP.NET Core's host hung on it (ADR 0009 addendum, items 14 to 16).
orbit run no longer starts a run whose worker sessions would all be refused for a plugin the policy does not allow (#22). orbit doctor failed claude.plugins for the managed plugins that the defaults (agents.allowed_plugins: [], agents.allow_managed_plugins: false) refuse, yet orbit run --foreground went on: about ten minutes of base-revision checks, baseline-exception questions and a planner worker, then two refused planner attempts and a refused curator (about 0.8 USD, nothing produced). Run start now makes doctor's judgement (one shared function, judgeWorkerPlugins): orbit run --foreground refuses in admission, before a run row, a frozen policy, a check or a model call exists, with every plugin id and doctor's exact fix line (exit 4, "No run was created"); a run handed to the service (--detach) or resumed is judged by its controller at the start of PREFLIGHT, before the base-revision checks, and ends BLOCKED with nothing spent (no check, no question, no worker, no curator). Why two places and not one: only the process that drives a run can judge its Claude Code environment, and a run that exists has already frozen the policy the fix would change, so refusing before it exists is the clean outcome whenever this process drives it; a BLOCKED run that cost nothing is the fallback when only the service can judge, and its message says the way out (allow the plugin and start a new run, or remove the plugin and orbit resume --force).
A worker session that Orbit refuses after it started (a plugin the policy does not allow, an MCP server, another permission mode) is not transient and is no longer retried as a worker attempt (#22): the planner, the reviewer and diagnosis units, and the implementer (also a parallel work unit) end the run BLOCKED after that one session. The outcome line carries the whole refusal, naming every plugin and the config line that allows it (it was cut at 200 characters, mid-sentence), and says that orbit doctor shows the cause. A run that ended this way with no candidate does not start the curator, which would be refused in the same environment (worker_refusal in the run outcome, "refused" in learning.json).
orbit init in a linked git worktree says that the exclude file it wrote is shared (#3 follow-up). git reads only the common git directory's info/exclude, so the rules always went to the main checkout's .git, outside the worktree, which surprised a person who expected init in a worktree to change nothing outside it. Orbit keeps that shared write (one write covers every worktree, nothing is committed) and now names the file and states that every worktree of the clone shares it, both when it adds the rules and when they are already there. orbit init --json gains exclude_file (path and shared_across_worktrees, true in a linked worktree and false in a normal checkout; detected with git by comparing --git-dir with --git-common-dir); exclude keeps its path and added. A normal checkout prints what it always did.
PREFLIGHT classifies every check that fails on the base revision before it calls one pre-existing (ADR 0010), and only a usage error from the check's own direct invocation of a tool counts: the command's program (after a leading env assignment, env, an npx-style runner or python -m) is the tool, the command is not a shell chain or pipeline, and what the error names is what the command names. An argument error (MSBuild's MSB1001 outside dotnet test and MSB1008, go's undefined flag or unknown command, cargo's unexpected argument) is a misconfigured check: the run blocks at PREFLIGHT with the check, the tool's error line and checks.<id>.command, as a frozen-policy block whose advice names a new run, not orbit resume --force, which runs the same command again and so blocks again unless the tool changed outside the policy; before, it was offered as a baseline exception (#23). A missing target (MSBuild's MSB1003, MSB1009 and MSB1011, dotnet test's MSB1001 (a test platform's option such as --report-trx before the repository runs that platform), npm's Missing script for the script the command runs when npm ran no script before it (no > acme@1.0.0 test banner), pytest's file or directory not found and its unrecognized arguments with exit 4 (an option of a plugin or conftest.py not there yet, such as --cov without pytest-cov or -n without pytest-xdist, which a goal may add), dotnet's or cargo's unknown command, a script of the repository the shell cannot find) goes on to CONTRACTING: when the contract names the check as the proof of a criterion its question is withdrawn and it is expected to flip, otherwise the run blocks there as misconfigured, with advice of its own by cause (a goal meant to create the target says so in a new run, a target a tool provides that is not installed or restored yet, such as a cargo plugin, a dotnet local tool or a pytest plugin, needs a new run once it is, a wrong command is corrected in checks.<id>.command; never orbit resume --force, which reads the same recorded baseline and blocks again), before the contract is written or any other check's question is settled. orbit resume refuses that block (exit 5) with the same advice and the foreground summary points to it, instead of sending a person to fix .orbit/config.yaml, which is the cause in one of the three; the notification of any frozen-policy block says to read the report, not to resume; and the outcome reason a run row keeps is cut in its evidence, never in the advice that ends it. A program the shell cannot find (exit 127) is an environment failure, cleared by installing it and orbit resume. An environment failure now also covers EACCES ("Permission denied") outside the checkout, a socket refused in a .NET tool's own crash (MSBUILD : error MSB1025 with SocketException (13): Permission denied, reproduced under srt on macOS from dotnet test of a project with two references; the fix named is -m:1 on a dotnet command that hands its arguments to MSBuild, as in the .NET entry below, and for dotnet format, which takes no -m:1, the check's command in the form that loads no project, as in the dotnet format entry below), a connection the sandbox's network proxy refused, NuGet's HTTP client failing to start in the sandbox (which the NIS domain name rule below prevents under the srt Orbit ships), and NuGet's restore that could not establish the SSL connection to nuget.org (on macOS nothing in the sandbox can verify its certificate; a review reached it as a pre-existing failure with a question by following the fix for the proxy's refusal, whose fix for a NuGet restore on macOS is now the cache filled outside the sandbox, and checks.dotnet-packages now fails when a mandatory check restores into an empty cache); MSBuild's N Error(s) no longer hides a restore the sandbox or its proxy refused on the base revision when every counted error is a restore error (NUxxxx) or MSB1025 (on a candidate that count stays the change's unless its base revision failed the same way). Such checks block with the first error line and a fix instead of becoming pre-existing failures with questions (#10); checks that share their evidence are named once, then the evidence once, and every sentence of a block reason starts with a capital letter. On a candidate those new denials count only when the same check showed the same one on the base revision, so a denial the change brings (a test that opens a file it may not read, a package from a host the check may not reach) goes to repair; Python unittest's FAILED (errors=1) and ERROR: test_x (...) now read as a failing test, as do Microsoft.Testing.Platform's report (failed X (12ms), failed: 1, printed by xunit v3, MSTest's runner and TUnit) and xunit's own ([FAIL], Failed: 1,), go test -json's "Action":"fail", bun's (fail) and dart's Some tests failed., so a test that fails on a path it may not read stays the code's, on the base revision and in repair; a footer-like line a check prints is its own output, only the log's last line is the runner's. The planner is told which checks are missing targets, and a baseline-exception question raised before this change is refused for a failure the environment stopped, read again from its log. None of these can be accepted as a baseline exception through orbit decide or a remote answer, a missing target included. A usage error printed by a script of the repository (npm test whose script runs a missing npm run lint, or cd client && npm test in a package with no test script), a chain (dotnet restore && npm test) or a program the check runs (dotnet run --project build/...) stays a pre-existing failure, as does output with a compile error or a failing test. orbit timeline names each check's classification and first error line, and a resumed baseline runs those checks again instead of reusing the failure it recorded before the fix.
.NET checks that build more than one project now run under srt (#10, reopened). MSBuild started a worker node for any restore or build with two projects to work on, and the node's named pipe (a Unix socket at /tmp/MSBuild<pid>) is refused by the sandbox: on macOS MSBuild then waited 30 s for each of ten node starts and failed after five minutes (MSB1025 and SocketException (13): Permission denied from dotnet test), on Linux it failed at once with no error. A dotnet check now pins one node in its own command with -m:1; Orbit does not change a check's processor count, since DOTNET_PROCESSOR_COUNT=1 would reach the test host, where xunit before 2.8 deadlocks a test that blocks on async code. orbit doctor fails a mandatory check (or dependencies.install_command) that runs dotnet build, test, publish, pack, restore, clean, msbuild or run itself (its command, the command env starts, or a command of a chain joined by && or ;, sh -c included) without -m:1 before any -- of dotnet test, unless its own env sets DOTNET_PROCESSOR_COUNT=1, before starting anything, and prints its command with -m:1 added (for dotnet run, a build with -m:1 and a run with --no-build, which it then passes), with the reason once for all such checks; it warns that it cannot tell for a check that runs MSBuild through make, a script or a shell line with a pipe or other shell syntax. A check that starts a worker node anyway is stopped as soon as MSBuild records the refused node, with the pipe and the fix for its command in its record, instead of after five minutes, and that record is read as the environment's failure (pipe-denied): on the base revision the run blocks at PREFLIGHT with the fix and no baseline exception question. On a candidate it counts only when the same check showed it on the base revision, as for the denials ADR 0010 added: a node the base revision did not start is the change's (a second project under a check without -m:1, a test that runs dotnet build), and it goes to repair. orbit doctor's .NET line now builds three generated projects (offline, no packages) in the check sandbox with the node switches MSBuild gets from that check's command, in its env, instead of running dotnet help. orbit init proposes dotnet build <target> -m:1 and dotnet test <target> -m:1. Workers in a .NET repository under srt are told to pass -m:1 (a worker's dotnet build without it waited the same five minutes). checks.dotnet-tests warns when a check relies on DOTNET_PROCESSOR_COUNT=1 in its own env and a test project uses xunit before 2.8. A toolchain the sandbox refuses, of any kind, now fails doctor instead of warning when a mandatory check runs its executable itself (ADR 0009 addendum).
.NET HTTP clients now work under srt on macOS. Each one reads the NIS domain name when it starts, which srt refused, so NuGet's restore failed with NU1301 ... The type initializer for 'System.Net.CookieContainer' threw an exception ... GetDomainName: -1, as did any test that makes an HTTP request. Orbit's preload adds one read-only Seatbelt rule, sysctl-read of kern.nisdomainname, to the checks, the dependency install, workers and doctor's probes that run .NET (with srt 0.0.78; with another version the record says it was not added), and to approved operations, the UI app and release commands (see the toolchain profile entry above). Those processes also get DOTNET_SYSTEM_NET_DISABLEIPV6=1, unless their command sets it: .NET opens dual-stack IPv6 sockets, so a connect to loopback arrives as ::ffff:127.0.0.1, which srt's Seatbelt loopback rule does not match on some macOS releases (GitHub's macOS runner; macOS 27 matches it), and there every .NET HTTP request in the sandbox, NuGet's restore through srt's proxy and a test's request to its own server on loopback, failed with Permission denied; with IPv4 sockets they connect, as srt arranges for Java. HTTPS from those sandboxes still cannot verify certificates on macOS, because the system trust service stays out of reach, so a restore from nuget.org in the dependency install fails there: fill the repository's NuGet cache outside the sandbox (NUGET_PACKAGES=<the cache doctor prints> dotnet restore -m:1), after which the install and the checks restore from it. On Linux the install downloads packages itself (ADR 0009 addendum).
orbit doctor says up front when a .NET repository's packages cannot be restored inside the sandbox (new checks.dotnet-packages, macOS under srt): for a repository whose projects have PackageReference items (or a packages.lock.json, Directory.Packages.props, packages.config, a local tool manifest, or an MSBuild SDK that NuGet resolves, such as Sdk="MSTest.Sdk/3.6.0"), it names the reason in one sentence (the system trust service is out of reach, so .NET cannot verify nuget.org's certificate) and the one command, with the real cache path, that fills the repository's NuGet cache outside the sandbox (the dependency install's own dotnet restore with -m:1, or dotnet restore -m:1 of the repository's solution or projects, then dotnet tool restore when the repository has local tools, whatever part of them the install restores); the install step and the checks then restore offline from it. It is a warning, and a failure when dependencies.install_command restores from the network and that cache is empty, or restores projects while a package version floats (13.*, which every restore looks up at nuget.org unless a lock file pins it; doctor names each). The toolchain dotnet line says the same of the cache on macOS, for a repository with packages (ADR 0009 addendum).
A .NET repository that treats warnings as errors no longer fails its dependency install and checks under srt on error NU1900 (#10). NuGet's vulnerability audit cannot reach nuget.org from the sandbox (on macOS nothing there can verify its certificate; on Linux a check has no network unless it lists the host), so every restore warned NU1900 after a wait, and with TreatWarningsAsErrors or -warnaserror that warning failed the install and every restoring check even after checks.dotnet-packages' command filled the cache, and the base revision's failure became a baseline exception question. The .NET profile now sets NuGetAudit=false (MSBuild reads it from the environment; a check's own env sets the variable either way) only where the audit cannot run: in every .NET process on macOS under srt, and in a check or worker whose sandbox does not let it reach api.nuget.org; doctor's probes follow the same rule. Where it can run it stays as the repository configures it, so Orbit does not report green where the repository's CI fails: the dependency install on Linux or in a container, and a Linux check that lists the host in its network_hosts, still fail a restore that treats NU1903 (a package with a known vulnerability) as an error. A check's record says the audit was off exactly when Orbit's setting reached it. A project or MSBuild import that sets NuGetAudit itself overrides the environment: the new checks.dotnet-audit (macOS under srt) fails such a repository when warnings are errors and the install or a mandatory check restores, warns otherwise, and names the change, <NuGetAudit Condition="'$(NuGetAudit)' == ''">true</NuGetAudit>, which keeps the audit on everywhere else. checks.dotnet-packages no longer says NU1900 is only a warning, and promises the offline restore only once the audit no longer fails it (ADR 0009 addendum, item 12).
A dotnet format check no longer waits and then becomes a baseline exception question under srt (#10). Every form but dotnet format whitespace --folder loads the project through MSBuildWorkspace's build host, whose named pipe .NET binds at /tmp/<guid> whatever TMPDIR says, which the sandbox refuses: on macOS (SDK 9) the format failed after the build host's 60 s connect timeout with a TimeoutException, on Linux (SDK 10) at once, and its implicit restore of a repository with project references was refused MSBuild worker nodes (five minutes before the early stop above). Nothing is opened for it: /tmp is shared by every process of the user, and Unix sockets in the check's private temp directory would not reach that path. orbit doctor fails a mandatory such check before any probe (warns for an optional one) with its command in the form that loads no project, dotnet format whitespace --folder --verify-no-changes, ready to paste, reading the folder of the solution or project it named and keeping its --include and --exclude, and says to run the style and analyzer checks outside Orbit; a check that also builds without -m:1 gets one command with both changes. With SDK 8 pinned by global.json, which loads the project in dotnet format's own process, doctor and the runner refuse only a format that restores first, and name a dotnet restore -m:1 before it and --no-restore (measured under srt). The runner reads the build host failure, and a stopped restore of such a check (whose fix it now names as that form, not -m:1), as an environment failure, so on the base revision the run blocks with the fix and no baseline exception question. The runner also looks for MSBuild's record of a refused node once a check has failed, so one that fails before its next scan (MSBuild fails at once on Linux) still gets the note and the fix. Under srt a check whose dotnet format reports the C# or Visual Basic project it names as in no language it supports (Could not format '<project>.csproj'. Format currently supports only C# and Visual Basic projects.) is recorded as failed with that fix, not passed: on Linux (SDK 10) that is how dotnet format sometimes reports the refused build host, and it exits 0 having checked nothing (8 runs of 40 in an Ubuntu container, and once on GitHub's runners). Workers in a .NET repository are told which form of dotnet format runs (ADR 0009 addendum). On macOS not even the folder form runs in a run's check sandbox: it lists every folder above the checkout for .editorconfig files, and a run's checkout sits in the read-denied Orbit home, so it died with UnauthorizedAccessException for <orbit home>/worktrees/<key>/<run> while doctor passed it. There doctor fails every mandatory dotnet format check that SDK 9 and later run, the folder form included, and doctor, the runner's note, the block reason and the worker note name running it outside Orbit, in CI, with SDK 8's pinned restore first as the other way.
A check whose SDK 8 dotnet format lost the output of its own dotnet --version probe is started again, and its pass is clean. dotnet format of SDK 8 reads that version before it loads anything, and when the child exits before it has begun to read the child's output (a race in dotnet format itself, also outside Orbit, more likely on a busy machine) it prints Unable to locate dotnet CLI. Ensure that it is on the PATH. and exits 4 having checked nothing: once in seven CI runs on GitHub's macOS runners, the pinned restore and --no-restore that doctor names for SDK 8 failed so, and reproduced under the runner and srt on macOS with SDK 8.0.303 by holding dotnet format's thread after it starts the probe, in a run's checkout and outside it alike. The runner now notes such an attempt and starts the check again, up to two more times beside its flaky_reruns; the attempt is kept, and it neither makes the pass flaky nor counts as the check's failure.