Skip to content

fix(runtime): refuse an unsafe agent provider home before planning, and let doctor --fix tighten it (#1265) - #1267

Open
mrthankyou wants to merge 5 commits into
unstablefrom
fix/1265-provider-home-preflight
Open

mrthankyou wants to merge 5 commits into
unstablefrom
fix/1265-provider-home-preflight

Conversation

@mrthankyou

@mrthankyou mrthankyou commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1265.

Problem

The agent adapters (templates/smithers/agents/provider-home.tsx) accept a provider home only if it is a directory the operator owns, with mode exactly 0700, below directories only the operator can write. Claude Code and Codex create their own homes from the umask, so the result is usually 0755 or 0775:

CLI umask Home it creates
Claude Code 2.1.207 (Linux) 0022 ~/.claude 755
Claude Code 2.1.207 (Linux) 0002 (Ubuntu desktop) ~/.claude 775
Codex 0.146.0 (Linux) 0022 ~/.codex 755
Codex (macOS, a maintainer's machine) 0022 ~/.codex 755

The Codex adapter uses its provider home in both subscription and api-key mode, and ultrafuzz init defaults to Codex. So a default Codex run fails for anyone whose Codex CLI created ~/.codex, on macOS or Linux, and a Claude run fails on Linux. It fails only when the engine renders the workflow, after about 2 minutes of planning and execution-snapshot preparation, as INTERNAL_ERROR: render frame: provider homes must be operator-owned with private 0700 permissions, with no directory named. CI misses it because the e2e campaign sets CODEX_HOME to a fresh 0700 directory.

Change

  • provider-home-preflight.ts:
    • predictedProviderHome mirrors the adapters' resolution order: config_dir or ULTRAFUZZ_PROVIDER_HOME_ROOT, then CLAUDE_CONFIG_DIR / CODEX_HOME / KIMI_*, then ~/.claude / ~/.codex / ~/.kimi-code, and ~/.ultrafuzz-provider-homes/<provider> for the rest.
    • providerHomeProblems applies the adapters' checks to each selected agent's home.
    • A test runs the adapter's own resolveProviderHome under Bun and checks the prediction matches it.
  • validate (and so run, which validates before planning) reports PROVIDER_HOME_UNSAFE in the agents check, naming the directory and the fix:

    CodexAgent would use /Users/…/.codex as its provider home, but /Users/…/.codex has mode 755; agents refuse a provider home that is not a private directory you own (mode 0700) under directories only you can write, so run ultrafuzz doctor --fix or chmod 700 /Users/…/.codex

  • ultrafuzz doctor gains a provider-homes check. ultrafuzz doctor --fix applies the repair first, then checks:
    • it sets each refused provider home that is a real directory the operator owns to 0700, and reports PROVIDER_HOME_FIXED (tightened <dir> from mode 755 to 700);
    • it never changes a directory above a provider home. That case gets a manual message: fix it, or point ULTRAFUZZ_PROVIDER_HOME_ROOT at a private directory.
  • When the check runs: it uses the launch environment's HOME, as the engine does. A launch without HOME, such as the unit tests' empty environments, isn't checked, so the suite never reads a developer's real ~/.codex.
  • Unchanged: the adapters' own checks in the engine stay as they are, as a second line of defense.
  • Docs: CLI reference (doctor, --fix), docs/config.md, CHANGELOG.

Not done: tightening the directory automatically during launch

Ultrafuzz could also chmod 700 an operator-owned ~/.codex or ~/.claude silently whenever it launches. That would need no user action, and it's arguably correct, since those directories hold credentials and both CLIs run as the same user. This PR deliberately doesn't do it. Per Chesterton's fence, the 0700 rule and the decision not to change other tools' directories should be reviewed first:

  • The directories belong to Claude Code and Codex, not Ultrafuzz.
  • A silent change could break setups where another user or tool reads the directory through group permissions (shared groups, backup or sync tools).
  • The operator wouldn't know it happened.

doctor --fix gives the same result with the operator's consent. If maintainers agree automatic tightening is safe, it can be added on top of this change. A related option is to accept an operator-owned home that isn't group- or world-writable (such as 0755), as the checks on parent directories already do. That also changes the security rule, so it also needs review.

Verification

  • Real CLI on macOS, fresh project (Codex default):
    • validate against the real HOME reports the maintainer's real ~/.codex (755) as PROVIDER_HOME_UNSAFE (read only; not modified).
    • With a throwaway HOME whose .codex is 755: doctor reports provider-homes: error; doctor --fix reports PROVIDER_HOME_FIXED … from mode 755 to 700; validate then passes.
  • New provider-home-preflight.test.ts:
    • the resolution order;
    • 755, 775, symlinked and group-writable-ancestor homes refused, and private or missing homes accepted;
    • --fix tightens only the operator's own home and leaves an ancestor alone;
    • validate refuses before launch and passes once fixed;
    • the prediction matches the adapter's resolveProviderHome run under Bun.
  • Existing tests: the runtime doctor/validate tests (12) and the CLI doctor tests pass. Strict eslint, prettier and the docs check are clean.

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

The PR should not merge while its advertised provider-home repair command is unavailable.

Summary

The PR adds provider-home validation before planning and changes doctor to report manual remedies. Changes since the previous review also add early duplicate-run detection, timeout-shadowing warnings, and retry handling for failed optional producers, including Modal resume decisions.

  • The promised doctor --fix repair is no longer available.
  • Node timeout warnings miss a longer group default shadowed by a node pin.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[validate or run] --> B[Check selected provider homes]
  B -->|unsafe| C[PROVIDER_HOME_UNSAFE]
  C --> D[Manual permission repair]
  B -->|safe| E[Plan and launch]
  F[doctor] --> B
Loading

Reviews (4) · Last reviewed commit: "Merge remote-tracking branch 'origin/uns..."

…nd let doctor --fix tighten it (#1265)

Claude Code and Codex create ~/.claude and ~/.codex from the umask (0755
or 0775), and the agent adapters accept only 0700. Any such home failed
the launch only when the engine rendered the workflow, after planning and
the execution snapshot, as an INTERNAL_ERROR naming no directory. This hit
the default Codex setup on macOS and Linux, and Claude on Linux.

validate (and so run) now predicts each selected agent's provider home with
the adapters' rules and reports PROVIDER_HOME_UNSAFE with the directory and
the fix. doctor reports a provider-homes check, and doctor --fix sets each
such home that is a real directory the operator owns to 0700. A directory
above a provider home is reported but never changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mrthankyou
mrthankyou requested a review from a team as a code owner October 2, 2026 02:39
Comment thread packages/runtime/src/provider-home-preflight.ts Outdated
Comment thread packages/runtime/src/provider-home-preflight.ts
Comment thread packages/runtime/src/provider-home-preflight.ts Outdated
Comment thread packages/runtime/src/provider-home-preflight.ts Outdated
Comment thread packages/runtime/src/provider-home-preflight.ts Outdated
… and pin the directory doctor --fix changes (#1265)

From review:
- The adapters require the provider-home root (~/.ultrafuzz-provider-homes
  or ULTRAFUZZ_PROVIDER_HOME_ROOT) to be private too, not only the home.
  It is now checked, and doctor --fix repeats until root and home are both
  tightened.
- A relative ULTRAFUZZ_PROVIDER_HOME_ROOT, which the adapters reject, is
  reported.
- A component that cannot be inspected for any reason other than ENOENT is
  reported instead of passing, as the adapter fails on it.
- doctor --fix opens the directory without following a link and changes it
  through that descriptor, only if it is still the directory checked.
- A caller that passes no environment is checked against the process's own,
  which the engine then uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread packages/runtime/src/provider-home-preflight.ts Outdated
Comment thread packages/runtime/src/provider-home-preflight.ts
Comment thread packages/runtime/src/provider-home-preflight.ts Outdated
…dable homes, and documents the root (#1265)

From review:
- A provider-home root shared by several agents is tightened once, and a
  directory already at 0700 is not reported as tightened.
- A home its owner cannot read (e.g. 0300) cannot be opened; it is changed
  by path instead, right after confirming it is still the same directory.
- The docs said --fix never changes a directory above a provider home, but
  it tightens the Ultrafuzz provider-home root; they now say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aviggiano

Copy link
Copy Markdown
Collaborator

Thanks, the preflight is the right fix. Failing at validate / run with PROVIDER_HOME_UNSAFE and the directory name is exactly what #1265 needed.

One change before we merge: please drop doctor --fix. We want doctor to stay report-only. It should list problems, not repair them. Changing permissions on the user's home config directories (fd-pinned fchmod, the EACCES path fallback, the multi-pass re-check loop) is a lot of code to maintain for a one-line chmod 700 <dir> the user can run themselves.

Concretely:

  • Remove fixProviderHomes, tightenDirectory, sameDirectory, ownedByOperator, DoctorInput.fix, the CLI flag, PROVIDER_HOME_FIXED / PROVIDER_HOME_FIX_FAILED, the fixable field, the fix-only tests, and the --fix docs and CHANGELOG text.
  • Drop the extra provider-homes doctor check too. doctor already reports PROVIDER_HOME_UNSAFE through validateProject's diagnostics.
  • Have the diagnostic print the exact chmod 700 <dir> command.

We'll take care of the smaller items ourselves in a follow-up after merge, so no need to handle them here:

  • Run lifecycle-inspection.test.ts with a temporary HOME. It now fails locally on any machine with a 0755/0775 ~/.codex.
  • Add OpenRouterAgent to PROVIDER_HOMES.
  • Reject a relative CODEX_HOME / CLAUDE_CONFIG_DIR / KIMI_* the way the adapter does.

thankyou and others added 2 commits October 2, 2026 09:56
…OVIDER_HOME_UNSAFE (#1265)

Per review, doctor stays report-only: drop doctor --fix and everything that
supported it (fixProviderHomes, the fd-pinned fchmod and its EACCES path
fallback, the multi-pass loop, DoctorInput.fix, the CLI flag,
PROVIDER_HOME_FIXED / PROVIDER_HOME_FIX_FAILED, the fixable field and the
fix-only tests), and the separate provider-homes doctor check, since doctor
already reports PROVIDER_HOME_UNSAFE through validate.

Each problem now carries the remedy the diagnostic prints: `chmod 700 <dir>`
for a home or provider-home root with the wrong mode, `chmod go-w <dir>` for
a writable ancestor, and what to change otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-home-preflight

# Conflicts:
#	CHANGELOG.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A 0755 or 0775 ~/.codex or ~/.claude, as the CLIs create them, fails every launch late with an engine INTERNAL_ERROR that names no path

2 participants