Repository navigation
fix: pre-trust worktrees in the config file Claude actually reads; say why a task is blocked - #825
Merged
Merged
Conversation
… reads
A task routed to a non-default Claude profile went to blocked ~25s after
start, on Claude Code's "Quick safety check" workspace trust dialog.
`ty show` printed only "Because: no host in hosts.yaml serves <project>",
which read as the cause of the block. It wasn't.
Root cause: the claude-profile-router plugin routed the task to
CLAUDE_CONFIG_DIR=<dir>. ensureProjectTrusted wrote the trust to
ClaudeConfigFilePath(dir) == "<dir>.json", but Claude launched with
CLAUDE_CONFIG_DIR=<dir> reads "<dir>/.claude.json". So the pre-trust went
to a file Claude never opens, and every task routed to a non-default
profile reached the trust dialog. ClaudeConfigFilePath now returns
<dir>/.claude.json for a non-default dir (and a legacy <dir>/.config.json
when present, as Claude does). The default ~/.claude still maps to
~/.claude.json, because ty never exports the default dir.
Also:
- Name the current trust dialog wording in the blocking-prompt detector,
so a task parked there says "Waiting on Claude Code's workspace trust
prompt" instead of the generic "waiting on a dialog".
- ty show: print the reason from the transition that blocked the task
("Blocked:"), and print placement as "Placement: local — <reason>" in
place of the "Ran on:"/"Because:" pair. JSON gains blocked_reason and
blocked_detail.
- ty-on: regression tests that a host which doesn't serve the project
(prefix-similar names, blank checkout) is never chosen or offered, and
that a missing inventory resolves local.
- Correct the QA stress script's notes, which documented the old path
mismatch as a constraint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bborn
force-pushed
the
fix/trust-prompt-and-placement-note
branch
from
October 3, 2026 09:19
63117c9 to
94c76d1
Compare
…der dash TestFinishedRemoteTaskSessionsAreEnded has failed on CI since #823 while passing on macOS and Arch. The fake ssh ran `FAKE_HOST=$host PATH=... eval "sh -c $inner"`. bash passes prefix assignments on to what eval runs, but dash (Ubuntu's /bin/sh) treats eval as a special builtin and keeps them in the current shell unexported, so the fake tmux saw an empty FAKE_HOST, matched no windows, and the sweep ended nothing. Export them instead. Test-only; the sweep's own script is POSIX and unaffected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
What happened
A task routed to a non-default Claude profile went to
blockedabout 25s after it started.ty showprinted:That line reads like the reason for the block. It is only the placement resolver keeping the task local, which was correct: none of the hosts has a checkout of the project. The real cause was Claude Code's "Quick safety check" workspace trust dialog in the fresh worktree.
Root cause (verified)
Routed to Claude profile ~/.claude-work (by plugin "claude-profile-router"), and the task'sclaudeprocess hadCLAUDE_CONFIG_DIR=~/.claude-work.ensureProjectTrustedwrites toClaudeConfigFilePath(dir), which returneddir + ".json", so~/.claude-work.json. That file holds only TaskYou's ownhasTrustDialogAccepted/hasCompletedProjectOnboardingentries.$CLAUDE_CONFIG_DIR/.claude.json. It uses~/.claude.jsononly when the variable is unset, and a.config.jsonin the config dir takes precedence (To()/Far()in the binary). So the pre-trust went to a file Claude never reads.~/.claude-work/.claude.jsonshow the project'shasTrustDialogAcceptedstayedfalseuntil the dialog was answered by hand..task-worktrees/wt, a.claudesymlink,settings.local.jsonpre-approvingRead(.claude/attachments/**), and a non-defaultCLAUDE_CONFIG_DIR. Results:<dir>.json(the old ty behaviour): dialog.<dir>/.claude.jsonfor the main repo path: no dialog, with thesettings.local.jsonpermission present.ensureProjectTrusted: no dialog.So the pre-approved permission in
settings.local.jsonis not the trigger. The dialog only lists it. Writing the trust to the right file is enough. The default profile was never affected, because~/.claude+.jsonis~/.claude.json, and ty never exports the default dir.Changes
ClaudeConfigFilePathreturns<dir>/.claude.jsonfor a non-default dir, a legacy<dir>/.config.jsonwhen that exists, and~/.claude.jsonfor the default. This also fixescopyMCPConfigandpurge-claude-config --config-dirfor profiles, which were reading and writing the same dead file.blockingPromptPatterns. A task parked there now recordsWaiting on Claude Code's workspace trust prompt — choose "Yes, I trust this folder" to let the task startinstead of the genericExecutor is waiting on a dialog.ty show:Blocked:line shows the reason and evidence from the transition that blocked the task.Placement: local — <reason>(orPlacement: <host> — <reason>) in place of theRan on:/Because:pair, so it no longer reads as a block reason.--jsongainsblocked_reasonandblocked_detail.ran_on/ran_on_reasonare unchanged.Rendered
ty show(branch binary against a DB copy, with a trust-prompt block event added):Tests
TestClaudeConfigFilePath, a stricter custom-dir subtest inTestEnsureProjectTrusted,TestDetectBlockingPromptNamesTheTrustDialog(real 2.1.288 capture),TestPlacementLine,TestBlockReason*, and ty-onTestResolveNeverPlacesOnAHostThatDoesNotServeTheProjectandTestResolveWithNoInventoryRunsLocal.go test ./...fails the same 7 tests on this branch as onorigin/main: the executor phantom-worktree and remote-worktree script tests, reaperTestLivePanePIDs_HonorsAgentSocket, and uiTestDetectMimeType_FixesPreviouslyBrokenFiles. They are environment-specific on this machine and unrelated to this change. ty-on (extensions/ty-on, its own module) passes. The new tests pass under-race.Not covered
ty place <id> <host>,--on <host>) is still the user's choice. It is not checked against the inventory, only by the host preflight at launch.~/<profile>.jsonfiles that ty wrote under the old path are left in place. They are harmless.🤖 Generated with Claude Code