fix(runtime)!: move the default provider-home root to ~/.ultrafuzz-provider-homes - #1242
Merged
Merged
Conversation
…ovider-homes Under Ubuntu's default umask 0002, ~/.local is 0775. The provider-home check refuses a group- or world-writable directory above a provider home unless it is sticky, so it refused the old default root, ~/.local/state/ultrafuzz/provider-homes. OpenRouterAgent, DeepSeekAgent, and a ClaudeAgent, CodexAgent or KimiAgent with a config_dir could not start (#1236). Without ULTRAFUZZ_PROVIDER_HOME_ROOT, the root is now ~/.ultrafuzz/provider-homes, directly under HOME. The adapter creates each missing directory itself with mode 0700 and chmods it to 0700, so the mode does not depend on the umask. XDG_STATE_HOME no longer selects the root. The check itself is unchanged, so an explicit root below a group-writable directory is still refused. Nothing is read from or moved out of the old root. data-governance.ts reads a configured provider's route config from that home, so it computes the same root. The template now reads HOME before os.homedir(), as data-governance.ts does. Bun's os.homedir() keeps the HOME its process started with, so this also lets the Bun contract test point the default root at a fixture home. Co-Authored-By: Claude Opus 5.5 <[email protected]>
~/.ultrafuzz is not a directory the adapters own. `ultrafuzz init` creates a project's .ultrafuzz with the umask, so under Ubuntu's 0002 a project at HOME leaves ~/.ultrafuzz 0775. The unchanged ancestor check then refused ~/.ultrafuzz/provider-homes for every default-root agent in every project. The root is now ~/.ultrafuzz-provider-homes, whose only ancestors are /, /home and HOME. The CHANGELOG migration now moves the old root from a custom XDG_STATE_HOME too. For a new login it creates the home under umask 077, since Codex refuses a CODEX_HOME that does not exist and a plain mkdir -p under umask 0002 recreates #1236. It names ULTRAFUZZ_PROVIDER_HOME_ROOT as the replacement for XDG_STATE_HOME, with the canonical homes that variable also moves. Co-Authored-By: Claude Opus 5.5 <[email protected]>
aviggiano
force-pushed
the
claude/provider-home-default-root
branch
from
October 1, 2026 00:19
e6a9e95 to
a0d1b51
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
resolveProviderHomeinpackages/runtime/src/templates/smithers/agents/provider-home.tsxrefuses every group- or world-writable directory above a provider home unless it is sticky. WithoutULTRAFUZZ_PROVIDER_HOME_ROOT, the root was$XDG_STATE_HOME/ultrafuzz/provider-homes, or~/.local/state/ultrafuzz/provider-homeswhenXDG_STATE_HOMEis unset.Ubuntu's default umask
0002makes~/.local0775(drwxrwxr-x ubuntu:ubuntuon the test host), so that root fails withprovider-home ancestors cannot be group/world writable.OpenRouterAgentandDeepSeekAgentalways use the root.ClaudeAgent,CodexAgentandKimiAgentuse it when they setconfig_dir. On such a host, those agents cannot start.Change
~/.ultrafuzz-provider-homes. It sits directly under the home directory, so on a normal host its only ancestors are/,/homeand the home directory, and no other directory Ultrafuzz creates sits above it. The adapter still creates each missing directory withmkdirSync(…, { mode: 0o700 }). It now also runschmodSync(…, 0o700), asopenrouter.tsxalready does for its config directory.XDG_STATE_HOMEwas unset, the new root's ancestors are a subset of the old root's, so any host where the old default passed the check passes with the new one. This includes the Modal sandbox, whose API-key rows run withHOME=/home/ubuntuand noULTRAFUZZ_PROVIDER_HOME_ROOT.~/.ultrafuzz/provider-homes, which this PR's first revision used.ultrafuzz initcreates a project's.ultrafuzzwith the umask. With the home directory as the project, umask0002leaves~/.ultrafuzz0775, and the check then refused that root for every default-root agent in every project (Greptile's P1).assertSafeDirectoryis byte-identical tomain. An explicit root below a group-writable directory is still refused, and so is a pre-existing group-writable~/.ultrafuzz-provider-homes.XDG_STATE_HOMEno longer selects the root. I chose to ignore it rather than honor an explicit value:ULTRAFUZZ_PROVIDER_HOME_ROOTis the Ultrafuzz-specific override, and it stays.~/.local/state, spelled out. Honoring it would leave Provider homes are refused on Ubuntu's default umask because ~/.local is group-writable #1236 in place for those operators.XDG_CACHE_HOME. A cache has no ancestor check.data-governance.tscomputes the same root.providerHome()reads a configured Claude, Codex or Kimi home'ssettings.jsonorconfig.tomlto derive the route that planning acknowledges. It had its own copy of the XDG default. Left alone, planning would read the old directory while the adapter used the new one.HOMEbeforeos.homedir(). This is the expressiondata-governance.tsalready uses (env.HOME?.trim() || os.homedir()). The template now uses it for both the default root and the canonical homes (~/.claude,~/.codex,~/.kimi-code).os.homedir()keeps theHOMEits process started with. I found nothing in the runtime, the templates or Smithers that rewritesHOME, so production resolves the same paths as before.~/.ultrafuzz-provider-homes..ultrafuzzproject directory thatinitcreates when the home directory is a project, not a child of it.--projector the working directory. It never searches upward for.ultrafuzz.cleanremoves onlyruns/…,artifacts/…andworkspaces/…below a project's.ultrafuzz.Who loses state in the old root
OpenRouterAgentandDeepSeekAgentauthenticate with API keys. Their homes hold session history and, for OpenRouter, aconfig.tomlthat the adapter rewrites on every start. They start with a fresh home, and there is nothing to migrate.ClaudeAgent,CodexAgentandKimiAgentwith aconfig_dir(and noULTRAFUZZ_PROVIDER_HOME_ROOT) find their home empty.auth = "subscription", their login lived there, for example Codex'sauth.json.config.tomlthat selects a route.mv "${XDG_STATE_HOME:-$HOME/.local/state}/ultrafuzz/provider-homes" ~/.ultrafuzz-provider-homes. The moved directories keep their0700.(umask 077; mkdir -p ~/.ultrafuzz-provider-homes/<provider>/<config_dir>), then log in with it asCLAUDE_CONFIG_DIR,CODEX_HOMEorKIMI_CODE_HOME. Codex refuses aCODEX_HOMEthat does not exist, and a plainmkdir -punder umask0002makes the directories0775, which recreates Provider homes are refused on Ubuntu's default umask because ~/.local is group-writable #1236.XDG_STATE_HOMEto relocate these homes setsULTRAFUZZ_PROVIDER_HOME_ROOTinstead. That variable also moves the home of a Claude, Codex or Kimi agent without aconfig_dir, from~/.claude,~/.codexor~/.kimi-code(orCLAUDE_CONFIG_DIR,CODEX_HOMEorKIMI_CODE_HOME) to<root>/claude,<root>/codexor<root>/kimi.~/.claude,~/.codex,~/.kimi-code, andCLAUDE_CONFIG_DIR,CODEX_HOMEorKIMI_CODE_HOMEwhen set.docs/config.mdalso gives theumask 077form for creating a home to log in to.Why not #1240
#1240 keeps the default root and teaches the check to accept a group-writable ancestor.
/etc/passwd,/etc/groupand/etc/nsswitch.conf, and it probes ACLs by running/bin/ls.systemToolsexemption in the adapter-boundary gate.lsbuilt without ACL support prints no+, so a directory whose ACL lets another account write would be accepted.ls, no/bin/ls, or LDAP/SSSD accounts.This PR leaves the check alone and moves the default to a directory the check already accepts. The product code changes by +10/−17 lines.
Verification
Tests. Each one fails on
origin/main. I putmain'sprovider-home.tsx, ormain'sdata-governance.tswithdist-testrebuilt, into this tree.provider-home.test.tshas a new test, the default provider-home root is a private directory directly under HOME that XDG_STATE_HOME does not move.0002.HOMEis0750, its.localis0775, as on Ubuntu, and its.ultrafuzzis0775, asultrafuzz initleaves it when the home directory is a project.XDG_STATE_HOMEis$HOME/.local/state.0700, also under umask0o277.config.tomlin the configured home.ULTRAFUZZ_PROVIDER_HOME_ROOTat the old default path, below the0775.local, is still refused.provider-home ancestors cannot be group/world writableonmain's template and on the first revision's (~/.ultrafuzz/provider-homes), under umask0002and022.runtime.test.ts, Bun adapter contract: generated OpenRouter adapter preserves opaque model IDs…0002withoutULTRAFUZZ_PROVIDER_HOME_ROOT. It uses a fixtureHOMEwith a0775.local, andXDG_STATE_HOMEpointed at its.local/state.CODEX_HOMEis<home>/.ultrafuzz-provider-homes/openrouter/openrouter-test-codexand that every directory up toHOMEis0700.main's template it fails with the same message, under umask0002and022. I ran it with a scratch startingHOME.Mutants. Each one is killed:
data-governance.tsonmain's root or on the first revision's, the route assertion getsmodel:openai.chmodSyncremoved,mkdirgives0500under umask0o277, and the test fails withprovider homes must be operator-owned with private 0700 permissions.os.homedir()instead ofHOME, the Bun test fails:CODEX_HOMElands under the process's startingHOME.XDG_STATE_HOMEor relax the check to accept group-writable directories.Migration commands, run against the template under umask
0002with fixture homes:mvabove, withXDG_STATE_HOMEunset and with a custom one: the root is accepted andauth.jsonis kept.(umask 077; mkdir -p ~/.ultrafuzz-provider-homes/codex/teams/codex): accepted. A plainmkdir -p: refused.CODEX_HOME:Error loading configuration: CODEX_HOME points to "…", but that path does not exist.Suites. This head is rebased on
42af2ff6, anddist-testwas rebuilt from it.provider-home,data-governanceandagent-adapter-boundaries: 23/23 under umask0002and022, including fix(runtime)!: keep the Forge guard under group-writable umasks, warn about old cloud runs' Modal storage in clean, and drop the smithers shim #1235's provider-home test.bun test dist-test/test/runtime.test.js --test-name-pattern '^Bun adapter contract:'): 51 pass, 1 skip, 0 fail under umask0002and022. The skip is the existingtestWhen(false)registration check.node scripts/run-tests.mjs supporting), umask022: 940 pass, 0 fail.~/.ultrafuzzand~/.ultrafuzz-provider-homesdid not exist on the test host before or after these runs, so no test wrote into the real home.Gates. I checked each exit code directly, and all are 0:
pnpm -w format:checkpnpm -w lintCI=1 ESLINT_PLUGIN_DIFF_COMMIT=origin/main pnpm -w lint:strict:cipnpm -w knippnpm --filter @ultrafuzz/runtime typechecknode scripts/docs-check.mjsRisk
ultrafuzz initbefore planning. Planning's usual refusal says so.resume --refresh-controller.XDG_STATE_HOMEis ignored. An operator who used it to relocate provider homes must now setULTRAFUZZ_PROVIDER_HOME_ROOT, which also moves the homes without aconfig_dir, as before.~/.ultrafuzz-provider-homesis refused, with the existing message, which does not name the directory. Ultrafuzz never creates one; a plainmkdir -punder umask0002does, which is why the CHANGELOG anddocs/config.mdgive theumask 077form.main. A sticky world-writable directory that another account owns passes as an ancestor, and that account can rename entries in it after validation.HOMEand the directories above it, since the root is a direct child ofHOMEand must itself be the operator's and0700. WithHOME=/tmp, another account can no longer own a directory betweenHOMEand the root, as it can own/tmp/.localonmain. It could pre-create the root, but the owner check then refuses it. This is from reading the code; I did not test it with a second account.main.HOMEfirst. They resolve differently only in a process that changesHOMEafter it starts.docs/reference/agent-adapter-boundaries.mdis unchanged, because it never named the old default. The boundary policy forprovider-home.tsx(no responsibilities) still holds:chmodSyncis not a filesystem-walking API, and the boundary suite passes.Closes #1236
🤖 Generated with Claude Code
The PR appears safe to merge; no new actionable issue or outstanding previous finding remains.
Summary
The PR moves the default provider-home root directly under
HOME, keeps data-governance route lookup aligned with the adapters, and documents the breaking change and migration. The revised tests cover a group-writable~/.ultrafuzz, private directory creation, and OpenRouter adapter behavior.Reviews (2) · Last reviewed commit: "fix(runtime)!: put the default provider-..."