Honor each agent's home relocation variable when resolving session dirs - #2375
Open
gtrrz-victor wants to merge 15 commits into
Open
Honor each agent's home relocation variable when resolving session dirs#2375gtrrz-victor wants to merge 15 commits into
gtrrz-victor wants to merge 15 commits into
Conversation
`entire session resume` restored transcripts to ~/.claude/projects even when CLAUDE_CONFIG_DIR relocated Claude Code's config directory, so Claude could not find the restored session, and on setups that moved the config dir to survive container rebuilds the restore landed on storage wiped by the next rebuild while reporting success. Claude Code documents that every ~/.claude path lives under CLAUDE_CONFIG_DIR when it is set. One resolver now answers where Claude's config dir is, and the session dir, session base dir, settings lookup and review-skill discovery all go through it. A relative override is refused rather than resolved against the working directory, which differs between a hook and a command the user runs from a subdirectory. Test harnesses unset CLAUDE_CONFIG_DIR so a developer's relocated config dir cannot outrank the HOME steer the attach fallback tests rely on. Fixes #2367 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M27ZYRPJK7DC21RCF4ZBSZ18
Gemini CLI resolves ~ through its own homedir() helper, which returns GEMINI_CLI_HOME when set, and every storage path, chats included, appends .gemini underneath it. Entire resolved the session dir and base dir from the real home only, so `entire session resume` restored transcripts to ~/.gemini while Gemini looked under $GEMINI_CLI_HOME/.gemini, and attach's cross-project fallback walked the wrong tree. One resolver now answers which home Gemini uses, and both session-dir methods go through it. The variable replaces the home directory, not ~/.gemini, so .gemini is still appended, unlike CLAUDE_CONFIG_DIR. A relative override is refused rather than resolved against a per-process working directory. Test harnesses unset GEMINI_CLI_HOME alongside CLAUDE_CONFIG_DIR so a developer's relocated home cannot outrank the HOME steer the attach fallback tests rely on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M280MCQZCQ05G2KY2S5J8350
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Four unresolved moderate test-isolation findings affect Gemini, Claude resume, and Factory Droid integration tests.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR makes session restoration and agent path discovery honor relocated directories for Claude, Gemini CLI, Cursor, and Factory Droid.
Changes:
- Adds relocation-aware path resolvers.
- Updates Claude settings and skill discovery.
- Adds documentation and unit/integration test coverage.
File summaries
| File | Summary |
|---|---|
docs/architecture/review-command.md |
Documents relocated Claude skill paths. |
docs/architecture/agent-guide.md |
Documents agent relocation variables. |
cmd/entire/cli/integration_test/setup_test.go |
Updates integration environment isolation. Open: scrub FACTORY_HOME_OVERRIDE. |
cmd/entire/cli/integration_test/resume_test.go |
Tests Claude resume with relocation. Open: isolate child HOME and USERPROFILE. |
cmd/entire/cli/global_test.go |
Clears relocation variables for CLI tests. |
cmd/entire/cli/agent/geminicli/gemini.go |
Adds GEMINI_CLI_HOME resolution. Open: clear it in the default-path test. |
cmd/entire/cli/agent/geminicli/gemini_test.go |
Tests Gemini path resolution. Open: clear GEMINI_CLI_HOME in the default-path test. |
cmd/entire/cli/agent/factoryaidroid/factoryaidroid.go |
Adds Factory Droid home resolution. |
cmd/entire/cli/agent/factoryaidroid/factoryaidroid_test.go |
Tests Droid path resolution. |
cmd/entire/cli/agent/cursor/cursor.go |
Adds Cursor data-directory resolution. |
cmd/entire/cli/agent/cursor/cursor_test.go |
Tests Cursor path resolution. |
cmd/entire/cli/agent/claudecode/generate.go |
Uses Claude config resolution for settings. |
cmd/entire/cli/agent/claudecode/discovery.go |
Discovers skills under relocated config. |
cmd/entire/cli/agent/claudecode/discovery_test.go |
Tests relocated skill discovery. |
cmd/entire/cli/agent/claudecode/claude.go |
Adds Claude config resolution for sessions. |
cmd/entire/cli/agent/claudecode/claude_test.go |
Tests Claude session path resolution. |
Review details
Suppressed comments (2)
cmd/entire/cli/agent/geminicli/gemini.go:126
- This resolver now consumes
GEMINI_CLI_HOME, but the existingTestGetSessionDir_DefaultPathonly clearsENTIRE_TEST_GEMINI_PROJECT_DIR. A developer shell with a relative override will now make that test fail, while an absolute override makes it exercise the relocated path instead of the default. Please clearGEMINI_CLI_HOMEin that default-path test (or in package test setup) so the default behavior remains hermetic.
if dir := os.Getenv(geminiHomeEnvVar); dir != "" {
if err := userdirs.RequireAbsoluteOverride(geminiHomeEnvVar, dir); err != nil {
return "", err //nolint:wrapcheck // the error already names the override and its value
}
return dir, nil
cmd/entire/cli/integration_test/resume_test.go:1187
- Because this test deliberately clears
ENTIRE_TEST_CLAUDE_PROJECT_DIR, the subprocess falls back to its home directory.cmd.Envinherits the developer's realHOME, so if the resolver regresses or otherwise ignoresCLAUDE_CONFIG_DIR, this test writes the restored transcript into the developer's~/.claudeand leaves it behind. Give the child a temporaryHOME(andUSERPROFILEon Windows) before exercising the fallback so the regression test remains hermetic.
cmd.Env = append(testutil.GitIsolatedEnv(),
"ENTIRE_TEST_CLAUDE_PROJECT_DIR=", // empty so the real resolution runs
"CLAUDE_CONFIG_DIR="+configDir,
)
- Files reviewed: 16/16 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ssion dirs The droid binary resolves ~ through getFactoryHome(), which returns FACTORY_HOME_OVERRIDE when set and os.homedir() otherwise, and its session store appends .factory/sessions/<project> underneath. Entire resolved the session dir and base dir from the real home only, so `entire session resume` restored transcripts to ~/.factory while Droid looked under $FACTORY_HOME_OVERRIDE/.factory, and attach's cross-project fallback walked the wrong tree. One resolver now answers which home Droid uses, and both session-dir methods go through it. Like GEMINI_CLI_HOME the variable replaces the home directory, not the dot-directory, so .factory is still appended. It is undocumented; it is read from the shipped binary, where it is the only relocation mechanism and the single input to getFactoryHome(). A relative override is refused rather than resolved against a per-process working directory. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M281REKSWZJV4A3B1W1R90E4
gtrrz-victor
force-pushed
the
fix-hardcoded-claude-home
branch
from
September 11, 2026 11:23
cf431ab to
bcfb126
Compare
Cursor keeps agent-transcripts under ~/.cursor even when CURSOR_DATA_DIR relocates its other data, verified locally against cursor-agent 2026.09.08. Its CLI bundle does resolve a data dir from the variable and advertises <data>/projects/<hash>/agent-transcripts to the model, which is what a static read of the bundle finds; the files themselves are written by the native file service, which stays on the real home. Following the variable would point resume, attach and owner detection at a directory Cursor never writes to. This comment exists so the bundle is not read that way again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Six agents resolved their relocation variable with six copies of the same dozen lines, and the copies had drifted into two policies: the three added on this branch refused a relative value, Codex, Copilot and pi accepted one, and nothing told a reader why CODEX_HOME=relative worked while CLAUDE_CONFIG_DIR=relative did not. dupl never fires on a body this short, so the next copy would have gone unflagged too. agent.ResolveHome(envVar, defaultRel) is now the only implementation. Every agent's resolver is a one-line call into it, so the policy is uniform: a relative value is refused for all six, and a blank value counts as unset again, which restores the TrimSpace the Claude settings lookup had before this branch and matches how the agents read their own variable. defaultRel records what each variable replaces, the dot-directory for Claude, Codex, Copilot and pi, the home itself for Gemini and Droid. The helper also refuses a variable missing from the static relocationEnvVars list, exposed as agent.RelocationEnvVars for the test harnesses. There is no interface to pin that list against, as there is for CallerSessionEnvVars, so enforcement runs the other way: an agent cannot start honoring a variable the harnesses do not scrub. The three near-identical per-agent table tests collapse into one on the helper. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M285F858B54WKRYTRA0FWZ2D
The two TestMains unset CLAUDE_CONFIG_DIR and GEMINI_CLI_HOME by hand under a comment that spoke of "the agents' homes" in general. Both lists now come from agent.RelocationEnvVars, so a developer's FACTORY_HOME_OVERRIDE, CODEX_HOME, COPILOT_HOME or PI_CODING_AGENT_DIR cannot outrank a test's HOME steer either, and the comment describes what the code does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M285FB7MH47K0WFWJY2GJP26
…bles Both the CLAUDE.md rule and the helper's own doc comment said it covered "all three trees an override can redirect", which stopped being the whole story once agent.ResolveHome started calling it for the agents' own variables. Scope the three-trees claim to Entire's directories and say why the agent case exists: nothing of Entire's is protected there, but a relative value resolves against the repo root inside a hook and against the user's cwd in a command, and Entire has to agree with where the agent wrote. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M285FE19BTAT2GKGBKZAFXY3
AgentForTranscriptPath swallowed a GetSessionDir error with a bare continue. That was invisible while the only error was a missing home directory; now that a relative relocation variable is refused, a user with one set has Claude Code silently stop owning any transcript path on every hook, and the only symptom is a session attributed to the wrong agent. Log the skip at debug with the agent and the error so .entire/logs names the cause. The refusal itself stays: resume and attach already surface it loudly, and resolving the value against a per-process cwd would scatter transcripts instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M285FGK1WTRMPK60J1BGWNEH
gtrrz-victor
force-pushed
the
fix-hardcoded-claude-home
branch
from
September 11, 2026 12:02
bcfb126 to
56a42e3
Compare
resolveCodexHome went through agent.ResolveHome, which refuses a relative value and treats a blank one as unset, while codexConfigPath (trust.go) and isUserHookRoot (hook_root.go) still read CODEX_HOME raw. One environment therefore meant three things inside one agent: session dirs refused it, trust inspection joined config.toml onto a cwd-relative path, and the user-wide hook-root check canonicalized it against whatever the process cwd was. Both sites now call resolveCodexHome and fail open the way they already did for an unresolvable home: config path empty, trust unknown; hook-root check false, root treated as the project's. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M2866B51ZX87ZQ3ZG962SMSC
The test cleared only the ENTIRE_TEST override, so with GEMINI_CLI_HOME exported it either failed on a relative value or quietly stopped exercising the home fallback on an absolute one. The cli TestMain scrub does not reach this package. Clear the variable here too, and pin the fallback layout instead of only asserting an absolute path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M287BEE0X8MCSRYQ6325CHG0
Contributor
Author
|
Copilot's summary listed two more items as suppressed comments without threads, both addressed:
|
The test clears the ENTIRE_TEST override so the real resolver runs, and inherited the developer's HOME. A regression that ignored CLAUDE_CONFIG_DIR would therefore have restored the transcript into the developer's own ~/.claude and left it there, which is what the first red run of this test did. Hand the child a throwaway HOME and USERPROFILE and assert nothing lands under it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M287XKCFTVEG0GPB9EYJ1RVY
ResolveHome trimmed the value it returned, and its comment claimed that is how the agents read their own variable. They do not: Cursor tests e?.trim() and then uses e, Claude reads process.env raw. So "/tmp/x " resolved to /tmp/x for Entire and /tmp/x for the agent, two directories on POSIX, and Entire looked in the one the agent never wrote to. Trim for the blank test only; return the raw value. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M288S9NPE8CTT86WEJ1G3D5Z
isUserHookRoot and codexConfigPath fail open when resolveCodexHome errors, which is right, but they did so silently. A refused CODEX_HOME then looked like a project hook root, or like trust that happened to be unknown, and nothing in .entire/logs said why. Log at debug, as AgentForTranscriptPath already does for the same skip. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Entire-Checkpoint: 01M288SC4SAVS59GE9YG1DRWM9
ResolveHome refuses a variable missing from relocationEnvVars, which
catches an agent that calls it with a new name. It cannot catch an agent
that never calls it and reads os.Getenv("NEW_AGENT_HOME") inline, which is
how Copilot's COPILOT_HOME read looked before the shared resolver. Such a
read escapes the list the test harnesses scrub, and the symptom is a test
that passes on CI and fails on the one contributor with the variable set.
The guard greps every os.Getenv and os.LookupEnv under the agent tree and
allows exactly three shapes: the resolver itself, the caller-session
reader, and the ENTIRE_TEST_* overrides each agent keeps for its tests,
whether spelled as a literal or through a named constant. Verified to fail
on a planted inline read before committing.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Entire-Checkpoint: 01M288SEJ2CVES2NR2NMZ7MKSX
gtrrz-victor
marked this pull request as ready for review
September 11, 2026 13:00
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.
entire session resumerestored transcripts to~/.claude/projects/...even whenCLAUDE_CONFIG_DIRhad relocated Claude Code's config directory, so Claude could not find the restored session (#2367). Every restore, attach fallback and transcript-owner lookup derives its directory from the agent'sGetSessionDir, so the fix lives in the agent packages.agent.ResolveHomeis now the one place that reads an agent's relocation variable, and every agent's session-dir methods go through it.CLAUDE_CONFIG_DIR~/.claude(documented)GEMINI_CLI_HOME~,.geministill appended (documented)FACTORY_HOME_OVERRIDE~,.factorystill appended (undocumented, read from the shipped binary)CODEX_HOME,COPILOT_HOME,PI_CODING_AGENT_DIRCursor gets no change: its CLI advertises a
CURSOR_DATA_DIRtranscripts folder, but the native writer stays under~/.cursor(verified locally). A comment on itsGetSessionDirrecords this so the bundle is not misread again.Decisions worth a look
userdirs.RequireAbsoluteOverride, rather than resolved against a cwd that differs between a hook andsession resume. This now applies to Codex, Copilot and pi as well.Tests. An integration test reproduces the issue's flow and was red before the fix. Per agent, a resolver table test and one honors-test per session-dir method. A grep guard fails if any agent reads a relocation variable outside
ResolveHome. Test harnesses scrubagent.RelocationEnvVars(). Not run: real-agent E2E.One commit per agent, then one per review finding; the commit messages carry the detail.
Fixes #2367
🤖 Generated with Claude Code