Skip to content

feat: support COMPOZY_HOME env override for home directory - #216

Merged
pedronauck merged 3 commits into
compozy:mainfrom
rodrigo123antunes:feat/compozy-home-override
Jul 2, 2026
Merged

pedronauck merged 3 commits into
compozy:mainfrom
rodrigo123antunes:feat/compozy-home-override

Conversation

@rodrigo123antunes

@rodrigo123antunes rodrigo123antunes commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a COMPOZY_HOME environment variable as an opt-in override for the Compozy home root. When set to a non-empty value, ResolveHomeDir resolves against that path instead of the implicit $HOME/.compozy, and every home-scoped consumer is routed through it so the override is honored consistently: ResolveHomePaths / EnsureHomeLayout, daemon startup, global config loading, global workspace-marker detection, extension discovery + enablement, and global reusable-agent discovery.

Why

The daemon today is a singleton per $HOME: every workspace that talks to ~/.compozy/daemon/daemon.sock serializes through one engine. As raised in #32 (comment from @rjsoux), users running multiple independent projects in parallel hit the same socket from every repo and have no first-class way to isolate them. Today the workaround is a "mirror home" that symlinks everything except .compozy, which is fragile and unsupported.

This PR delivers Tier 1 of the request: an official, documented escape hatch. Operators point one shell at one COMPOZY_HOME, one at another, and each gets its own daemon, socket, lock, state, and global DB. The CLI --home flag and Tier 2 (true parallel runs across workspaces inside one daemon) are intentionally out of scope and can layer on top.

What Changed

  • internal/config/home.go
    • New const HomeEnvVar = "COMPOZY_HOME".
    • New package-level osLookupEnv stub var, mirroring the existing osUserHomeDir pattern, so tests can override env resolution without touching the process.
    • New lookupHomeOverride() helper that returns (value, true) only when the variable is set to a non-empty, whitespace-trimmed string.
    • ResolveHomeDir precedence is now: COMPOZY_HOME (if set) → $HOME/.compozy (existing behavior).
    • ~ and ~/ prefixes inside COMPOZY_HOME are expanded against the current user's home via the existing ResolvePath / expandUserPath helpers, so COMPOZY_HOME=~/alt-compozy works.
  • Routed the remaining home-scoped consumers through ResolveHomeDir / ResolveHomePaths. These previously rebuilt $HOME/.compozy from os.UserHomeDir directly, so a set COMPOZY_HOME split-brained — the daemon isolated but these consumers stayed on $HOME/.compozy:
    • internal/core/workspace/config.go — resolveConfigPaths (global config path) and discoverGlobalWorkspaceMarkerDir (global workspace-marker detection).
    • internal/core/extension/discovery.go + internal/core/extension/enablement.go — the enablement store and discovery now root at the Compozy home (the .compozy / COMPOZY_HOME root) instead of appending .compozy to the OS home.
    • internal/cli/extension/root.go + internal/cli/extension/install.go + internal/cli/setup_assets.go — the ext flow now separates the OS user home (used only for setup.ResolverOptions: .claude / .codex / .config) from the Compozy home (install target, enablement store, discovery), resolving the latter via ResolveHomeDir.
    • internal/core/agents/agents.go — the global reusable-agent root now honors the override.
  • internal/config/home_test.go and the touched packages' tests
    • Coverage for: env override used, whitespace trimmed, empty ignored, whitespace-only ignored, ~ expansion, full ResolveHomePaths propagation, unset fallback.
    • Tests that touch env vars drop t.Parallel() (Go disallows combining it with t.Setenv); existing tests keep their parallelism.
    • Broadened COMPOZY_HOME guards across the affected packages' tests so an ambient COMPOZY_HOME in a developer's environment cannot leak into fixtures.

Behavior Notes

  • Backward compatible. No env var set → identical behavior to main (resolved against os.UserHomeDir + DirName).
  • Empty / whitespace-only COMPOZY_HOME is treated as unset. No silent surprises if a script exports COMPOZY_HOME="".
  • Relative paths in COMPOZY_HOME are made absolute via filepath.Abs, matching the rest of ResolvePath.
  • Cross-platform. Works on Linux, macOS, and Windows — env-var only, no shell expansion required.
  • Consistent isolation. The daemon socket/lock/info/log/runs/state/global DB, global config, global workspace-marker detection, extension discovery + enablement, and global reusable agents all resolve against the same root, so two daemons with different COMPOZY_HOME values do not collide and there is no split-brain between the daemon and config/extensions.
  • OS home is left untouched. Legitimate OS-home lookups — .claude, .codex, XDG (~/.config/compozy/state.yml), and path display — continue to use os.UserHomeDir and are intentionally unaffected by COMPOZY_HOME.
  • No CLI flag in this PR. The environment variable is the contract. A --home flag can be added later as a thin wrapper around the same lookup.

Testing

  • go test -race ./... — green for the touched packages (config, core/workspace, core/extension, core/agents, cli, cli/extension, daemon).
  • Several consumers previously rebuilt $HOME/.compozy via os.UserHomeDir and were explicitly routed through ResolveHomeDir / ResolveHomePaths so they honor the override consistently.

Verification

make fmt                          # clean
make lint                         # 0 issues
make go-build                     # clean
make test                         # green for the touched packages (-race)

Related

  • Addresses Tier 1 of the request in ✨ Feedbacks & Suggestions #32 (comment by @rjsoux).
  • Crosses paths with the planned parallel multi-run mode (Tier 2 of the same request); orthogonal change.

Checklist

  • make fmt clean
  • make lint clean (0 issues)
  • make test passes for the touched packages
  • Backward compatible (no env var → unchanged behavior)
  • Cross-platform (Linux/macOS/Windows)

Summary by CodeRabbit

  • New Features
    • You can now override the Compozy home directory with an environment variable (supports trimming whitespace and ~ path expansion).
  • Bug Fixes
    • Empty or whitespace-only overrides are ignored, and the app falls back to the standard user home.
    • Compozy-home–derived paths (including derived subpaths) now consistently follow the override when present.
  • Improvements
    • Extension discovery, enablement state storage, and related workspace/global configuration are now rooted in the Compozy home directory for more consistent behavior.

Add an opt-in escape hatch that lets operators point Compozy at a custom
home directory without relying on the implicit $HOME/.compozy layout.

Precedence in ResolveHomeDir:
  1. COMPOZY_HOME when set to a non-empty value (whitespace trimmed).
     Leading '~' or '~/' expands against the current user's home.
  2. $HOME/.compozy (unchanged).

This unblocks Tier 1 of the feedback in compozy#32: running multiple workspaces
against isolated daemons by giving each one its own COMPOZY_HOME, instead
of the current single-daemon-per-$HOME singleton. The CLI flag can layer
on top in a follow-up.

Verification:
  - gofmt, go vet, go build ./... clean
  - make fmt, make lint clean (0 issues)
  - go test -race ./internal/config/... passes
@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

COMPOZY_HOME now controls Compozy home resolution. Enablement storage, extension discovery, CLI extension commands, agent lookup, setup catalog loading, and workspace config path resolution all now use the Compozy home root or updated derived paths.

Changes

Compozy home resolution

Layer / File(s) Summary
Environment override plumbing
internal/config/home.go
Adds the COMPOZY_HOME constant and a stub-able os.LookupEnv reference for home lookup.
Home resolution behavior and tests
internal/config/home.go, internal/config/home_test.go
Updates ResolveHomeDir to prefer the trimmed environment override, then adds tests for direct use, whitespace trimming, tilde expansion, and fallback to the user home directory.

Enablement and discovery rooting

Layer / File(s) Summary
Enablement store paths
internal/core/extension/enablement.go, internal/core/extension/enablement_test.go
Enablement store construction now resolves COMPOZY_HOME when needed and stores user/workspace state directly under the resolved home directory, with tests updated for the new root and env-driven resolution.
Discovery home layout
internal/core/extension/discovery.go, internal/core/extension/discovery_test.go, internal/daemon/review_exec_transport_service_test.go
Extension discovery scans user extensions from <homeDir>/extensions, and the related tests now pass a Compozy home root into discovery and enablement setup.

CLI extension home propagation

Layer / File(s) Summary
CLI home dependency wiring
internal/cli/extension/root.go
The CLI resolves compozyHome, stores it on command environment state, and uses it for enablement creation and discovery input.
Extension install paths
internal/cli/extension/install.go
Install and uninstall paths now use env.compozyHome, and userExtensionsRoot returns <compozyHome>/extensions.
CLI test alignment
internal/cli/extension/display_test.go, internal/cli/extensions_bootstrap_test.go, internal/cli/root_command_execution_test.go
CLI tests and helpers now build enablement stores under <HOME>/.compozy and assert the new home-resolution dependency is wired.

Setup, agents, and workspace config

Layer / File(s) Summary
Setup catalog root resolution
internal/cli/setup_assets.go
Setup catalog loading now resolves only the workspace root, and workspace-root resolution trims CWD before falling back to os.Getwd().
Global agents root
internal/core/agents/agents.go, internal/core/agents/agents_test.go
Global agents resolution now uses compozyconfig.ResolveHomeDir and joins the resolved home path directly with the agent directory.
Workspace config path resolution
internal/core/workspace/config.go, internal/core/workspace/config_test.go
Workspace config discovery now resolves global marker and config paths through compozyconfig helpers, and the tests now isolate COMPOZY_HOME instead of stubbing home lookup.

Sequence Diagram(s)

sequenceDiagram
  participant commandDeps.resolveEnv
  participant extensions.NewEnablementStore
  participant discoverAll
  participant extensions.Discovery
  commandDeps.resolveEnv->>extensions.NewEnablementStore: resolve compozyHome for enablement store
  commandDeps.resolveEnv->>discoverAll: pass env.compozyHome as HomeDir
  discoverAll->>extensions.Discovery: construct discovery request
  extensions.Discovery->>extensions.NewEnablementStore: read enablement state rooted in Compozy home
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • compozy/compozy#78: Also changes global agent discovery path construction and home-root handling in internal/core/agents/agents.go.

Suggested reviewers

  • pedronauck
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding COMPOZY_HOME support to override home directory resolution.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rodrigo123antunes
rodrigo123antunes marked this pull request as ready for review June 27, 2026 02:13
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/config/home_test.go (1)

244-350: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Convert these cases into table-driven subtests.

This is one behavior matrix split across several top-level tests, so it misses the repo’s required t.Run("Should...") pattern and duplicates the same setup/assertion flow. Fold them into a single table-driven test with named subtests.

As per coding guidelines, **/*_test.go: Use table-driven tests with subtests (t.Run) as the default pattern. As per path instructions, **/*_test.go: MUST use t.Run("Should...") pattern for ALL test cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/home_test.go` around lines 244 - 350, These
ResolveHomeDir/ResolveHomePaths cases are the same behavior matrix split across
multiple top-level tests, so consolidate them into one table-driven test using
subtests. Refactor the existing TestResolveHomeDirUsesCompozyHomeEnv,
TestResolveHomeDirCompozyHomeEnvTrimsWhitespace,
TestResolveHomeDirIgnoresEmptyCompozyHomeEnv,
TestResolveHomeDirIgnoresWhitespaceCompozyHomeEnv,
TestResolveHomeDirCompozyHomeEnvExpandsTilde,
TestResolveHomePathsUsesCompozyHomeEnv, and
TestResolveHomeDirWithoutCompozyHomeFallsBackToUserHome into a single
t.Run-driven test with named cases following the required "Should..." pattern,
while preserving the current setup/assertion behavior for ResolveHomeDir,
ResolveHomePaths, stubConfigUserHomeDir, and HomeEnvVar.

Sources: Coding guidelines, Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/config/home_test.go`:
- Around line 334-341: The unset-COMPOZY_HOME test is relying on the real
process environment, so make
TestResolveHomeDirWithoutCompozyHomeFallsBackToUserHome hermetic by stubbing
osLookupEnv for HomeEnvVar (or explicitly unsetting it with cleanup) instead of
asserting the ambient state. Use the existing test helpers and symbols
ResolveHomeDir, osLookupEnv, and HomeEnvVar to keep the test independent of
machine-specific environment settings.

---

Nitpick comments:
In `@internal/config/home_test.go`:
- Around line 244-350: These ResolveHomeDir/ResolveHomePaths cases are the same
behavior matrix split across multiple top-level tests, so consolidate them into
one table-driven test using subtests. Refactor the existing
TestResolveHomeDirUsesCompozyHomeEnv,
TestResolveHomeDirCompozyHomeEnvTrimsWhitespace,
TestResolveHomeDirIgnoresEmptyCompozyHomeEnv,
TestResolveHomeDirIgnoresWhitespaceCompozyHomeEnv,
TestResolveHomeDirCompozyHomeEnvExpandsTilde,
TestResolveHomePathsUsesCompozyHomeEnv, and
TestResolveHomeDirWithoutCompozyHomeFallsBackToUserHome into a single
t.Run-driven test with named cases following the required "Should..." pattern,
while preserving the current setup/assertion behavior for ResolveHomeDir,
ResolveHomePaths, stubConfigUserHomeDir, and HomeEnvVar.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 81594f29-658a-4b16-a912-56b951804cca

📥 Commits

Reviewing files that changed from the base of the PR and between e43aa56 and 63a313f.

📒 Files selected for processing (2)
  • internal/config/home.go
  • internal/config/home_test.go

Comment thread internal/config/home_test.go Outdated
@greptile-apps

greptile-apps Bot commented Jun 27, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR adds COMPOZY_HOME as an environment-variable override for the Compozy home root directory, enabling daemon isolation per shell/project by routing all home-scoped consumers (daemon, config, extensions, agents, workspace-marker detection) through a single ResolveHomeDir() call that checks COMPOZY_HOME first and falls back to $HOME/.compozy.\n\n- Core change (internal/config/home.go): adds lookupHomeOverride() backed by a stubbable osLookupEnv var; ResolveHomeDir gains the env-variable precedence path with tilde expansion, whitespace trimming, and filepath.Abs normalization.\n- Consumer migration: internal/core/extension/{discovery,enablement}.go, internal/core/agents/agents.go, internal/cli/extension/{root,install}.go, internal/core/workspace/config.go, and internal/cli/setup_assets.go are updated to root at the Compozy home rather than rebuilding $HOME/.compozy from os.UserHomeDir.\n- Tests: new table-driven coverage in home_test.go; existing tests in all touched packages receive ambient COMPOZY_HOME guards (t.Setenv(HomeEnvVar, \"\")).

Confidence Score: 4/5

Safe to merge for production code; one test in config_test.go may fail non-deterministically on Linux CI runners where the OS user database provides a home directory even when HOME is empty.

The production path is well-structured and backward-compatible. The one concern is TestLoadConfigReturnsErrorWhenHomeLookupFails, which replaced a deterministic stub with t.Setenv("HOME",""). On Linux, os.UserHomeDir() falls back to the user database when HOME is empty, so the test may not trigger the expected error on typical CI machines, turning a formerly reliable assertion into a flaky one.

internal/core/workspace/config_test.go — the updated error-trigger test. internal/core/workspace/config.go — the globalRoot derivation for absolute COMPOZY_HOME paths.

Important Files Changed

Filename Overview
internal/config/home.go Core implementation: adds HomeEnvVar, osLookupEnv stub, lookupHomeOverride(), and wires env-variable precedence into ResolveHomeDir. Logic is correct and well-guarded for empty/whitespace/tilde inputs.
internal/config/home_test.go New table-driven test TestResolveHomeDirAndPathsRespectCompozyHome covers env-override, whitespace, tilde expansion, and fallback. Existing tests gain COMPOZY_HOME guards. Clean overall.
internal/core/workspace/config.go Removes the package-level osUserHomeDir stub and delegates to compozyconfig.ResolveHomePaths(); globalRoot is now filepath.Dir(homePaths.HomeDir). Has a known semantic change for absolute COMPOZY_HOME paths: relative add_dirs entries resolve against the parent of COMPOZY_HOME instead of $HOME.
internal/core/workspace/config_test.go Replaces deterministic stubWorkspaceUserHomeDir with t.Setenv("HOME","") in TestLoadConfigReturnsErrorWhenHomeLookupFails; on Linux, os.UserHomeDir() falls back to /etc/passwd when HOME is empty, so the test may not reliably trigger the expected error.
internal/core/extension/enablement.go Removes local osUserHomeDir var; empty-homeDir fallback now routes through compozyconfig.ResolveHomeDir(); userStatePath/workspaceStatePath drop the .compozy segment since callers now pass the Compozy home directly.
internal/core/extension/discovery.go Comment on HomeDir field updated; userRoot drops the .compozy/extensions suffix since the field now expects the Compozy home root directly. Change is consistent with the rest of the PR.
internal/core/agents/agents.go Default homeDir function switched from os.UserHomeDir to compozyconfig.ResolveHomeDir; globalAgentsRoot drops the .compozy path segment; net path unchanged for default layout, and COMPOZY_HOME is now honored.
internal/cli/extension/root.go Adds resolveCompozyHome dep (backed by compozyconfig.ResolveHomeDir) and splits commandEnv into homeDir (OS home for setup assets) and compozyHome (Compozy root for extension paths).
internal/cli/setup_assets.go Removes homeDir from Discovery initialization; empty HomeDir causes NewEnablementStore to fall back to config.ResolveHomeDir(), correctly honoring COMPOZY_HOME without explicitly threading it through.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[ResolveHomeDir] --> B{COMPOZY_HOME set\nand non-empty?}
    B -- Yes --> C[TrimSpace + tilde expand\nvia ResolvePath]
    B -- No --> D[os.UserHomeDir + DirName]
    C --> E[Compozy home root]
    D --> E

    E --> F[ResolveHomePaths]
    E --> G[NewEnablementStore]
    E --> H[Registry.globalAgentsRoot]
    E --> I[discoverGlobalWorkspaceMarkerDir]

    F --> J[resolveConfigPaths\nglobalRoot = filepath.Dir]
    F --> K[daemon startup\nsocket / lock / DB / runs]

    G --> L[Discovery.resolveEnablementStore\nsetup_assets / ext CLI]
    
    style B fill:#fffbe6,stroke:#d4a017
    style E fill:#e6f0ff,stroke:#4472c4
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[ResolveHomeDir] --> B{COMPOZY_HOME set\nand non-empty?}
    B -- Yes --> C[TrimSpace + tilde expand\nvia ResolvePath]
    B -- No --> D[os.UserHomeDir + DirName]
    C --> E[Compozy home root]
    D --> E

    E --> F[ResolveHomePaths]
    E --> G[NewEnablementStore]
    E --> H[Registry.globalAgentsRoot]
    E --> I[discoverGlobalWorkspaceMarkerDir]

    F --> J[resolveConfigPaths\nglobalRoot = filepath.Dir]
    F --> K[daemon startup\nsocket / lock / DB / runs]

    G --> L[Discovery.resolveEnablementStore\nsetup_assets / ext CLI]
    
    style B fill:#fffbe6,stroke:#d4a017
    style E fill:#e6f0ff,stroke:#4472c4
Loading

Reviews (3): Last reviewed commit: "feat: honor COMPOZY_HOME across config, ..." | Re-trigger Greptile

Comment thread internal/config/home_test.go Outdated
- Clear HomeEnvVar in stub-user-home tests so ambient COMPOZY_HOME
  cannot shadow the stubbed resolver path.
- Consolidate seven ResolveHomeDir/ResolveHomePaths cases into a
  single table-driven test with Should... subtests.
- Use stubConfigLookupEnv for the unset case instead of asserting
  on the runner environment.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 27, 2026
@rodrigo123antunes

Copy link
Copy Markdown
Contributor Author

@pedronauck review this

@pedronauck pedronauck left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Thanks for this — the home.go implementation itself is clean and correct (clear precedence, safe empty/whitespace fallback, ~ expansion, and the osLookupEnv stub mirrors the existing pattern). The daemon isolation that motivated #32 (socket/lock/db/runs/state) works as intended.

My concern is scope, not the new code: the PR body states that "every caller that flows through it — ... global config ... extension enablement, workspace config" inherits the override. That claim is not true for three consumers, because they build the .compozy path independently and never go through ResolveHomeDir. The result is a split-brain when COMPOZY_HOME is set (which is the whole point of the feature): the daemon isolates, but config / extensions / workspace detection stay on $HOME/.compozy.

Findings (most severe first)

1. internal/core/workspace/config.go:257 — global/workspace config does NOT respect COMPOZY_HOME.
resolveConfigPaths calls osUserHomeDir() directly (line 248) and then ResolveHomePathsFrom(filepath.Join(resolvedHomeDir, DirName)) — the explicit-base variant, which does not call ResolveHomeDir. So with COMPOZY_HOME=/foo, the daemon creates DB/socket under /foo, but config.toml is still read from $HOME/.compozy/config.toml. Daemon and config diverge — directly contradicts the "global config / workspace config" claim.

2. internal/core/extension/discovery.go:167 (and enablement.go:174-178) — extensions and enablement state do NOT respect COMPOZY_HOME.
Discovery{WorkspaceRoot: ...}.Discover calls NewEnablementStore(ctx, ""), which falls back to os.UserHomeDir(), and the scan joins ".compozy", "extensions" (hardcoded). Two daemons with different COMPOZY_HOME values share the same user extensions and the same .compozy-state.json enablement file — the opposite of the isolation promised under "extension enablement".

3. internal/core/workspace/config.go:153 — discoverGlobalWorkspaceMarkerDir ignores the override.
Builds filepath.Join(resolvedHomeDir, model.WorkflowRootDirName) from osUserHomeDir() directly (line 144). Same root cause as #1: global workspace marker detection stays on $HOME/.compozy even when the override is set.

4. (minor) PR body / internal/update/state.go — inaccurate "update state" claim.
The PR lists "update state" as covered, but it lives under ~/.config/compozy/state.yml (XDG), never under .compozy. Not a regression, but the claim is wrong.

Requested changes

  • Complete the feature — route resolveConfigPaths, discoverGlobalWorkspaceMarkerDir, and the extension homeDir through ResolveHomeDir so the override is honored consistently. The current fix is too shallow (only home.go); generalizing the underlying mechanism is preferable to leaving a partial feature.

Another Change

Tests in other packages (internal/core/sync, internal/daemon, etc.) that call ResolveHomePaths may become flaky if a developer has COMPOZY_HOME set in their environment — the t.Setenv(HomeEnvVar, "") guard was only added to home_test.go. Worth considering a broader guard.

…ents

ResolveHomeDir was the only consumer honoring COMPOZY_HOME. Global config
loading, global workspace-marker detection, extension discovery/enablement,
and global reusable-agent discovery each rebuilt the ~/.compozy path from
os.UserHomeDir, so a set COMPOZY_HOME split-brained: the daemon isolated but
those consumers stayed on $HOME/.compozy.

Route every home-scoped consumer through config.ResolveHomeDir/ResolveHomePaths:

- workspace/config.go: resolveConfigPaths uses ResolveHomePaths (globalRoot
  stays filepath.Dir(HomeDir) to preserve relative add_dirs bases);
  discoverGlobalWorkspaceMarkerDir uses ResolveHomeDir.
- extensions: EnablementStore and Discovery now root at the Compozy home (the
  ".compozy"/override root), not the OS home + hardcoded ".compozy". The CLI ext
  flow splits the OS home (setup.ResolverOptions: .claude/.codex/.config) from
  the Compozy root (install target, store, discovery) via a resolveCompozyHome
  dep.
- core/agents: globalAgentsRoot honors the override.
- cli/setup_assets: setup asset discovery resolves the Compozy home internally.

Broaden test COMPOZY_HOME guards so an ambient override cannot leak into
fixtures; legitimate OS-home lookups (.claude/.codex/XDG/path display) are left
untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/cli/extension/root.go (1)

100-129: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consistent home-resolution wiring; minor redundant lookup.

Logic is correct and matches the downstream NewEnablementStore/Discovery.HomeDir contract. Note that resolveHomeDir (os.UserHomeDir) and resolveCompozyHome (compozyconfig.ResolveHomeDir, which itself calls osUserHomeDir when COMPOZY_HOME is unset) both independently invoke the OS home lookup, causing a redundant syscall in the common case. Not a correctness issue, just avoidable duplication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/extension/root.go` around lines 100 - 129, The home-directory
resolution in commandDeps.resolveEnv is doing a redundant OS home lookup because
resolveHomeDir and resolveCompozyHome each resolve the user home independently.
Update the wiring so resolveCompozyHome can reuse the already-resolved homeDir
from resolveEnv (or otherwise centralize the lookup) while preserving the
existing commandEnv and discoverAll behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/cli/extension/root.go`:
- Around line 100-129: The home-directory resolution in commandDeps.resolveEnv
is doing a redundant OS home lookup because resolveHomeDir and
resolveCompozyHome each resolve the user home independently. Update the wiring
so resolveCompozyHome can reuse the already-resolved homeDir from resolveEnv (or
otherwise centralize the lookup) while preserving the existing commandEnv and
discoverAll behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3ce13401-eb34-4976-bb1e-43bcbd62c838

📥 Commits

Reviewing files that changed from the base of the PR and between 82c9191 and 7839398.

📒 Files selected for processing (15)
  • internal/cli/extension/display_test.go
  • internal/cli/extension/install.go
  • internal/cli/extension/root.go
  • internal/cli/extensions_bootstrap_test.go
  • internal/cli/root_command_execution_test.go
  • internal/cli/setup_assets.go
  • internal/core/agents/agents.go
  • internal/core/agents/agents_test.go
  • internal/core/extension/discovery.go
  • internal/core/extension/discovery_test.go
  • internal/core/extension/enablement.go
  • internal/core/extension/enablement_test.go
  • internal/core/workspace/config.go
  • internal/core/workspace/config_test.go
  • internal/daemon/review_exec_transport_service_test.go

@rodrigo123antunes

Copy link
Copy Markdown
Contributor Author

Thanks for digging into this @pedronauck, the scope critique was fair. The first pass only touched home.go, so the override worked for the daemon but not for the consumers that build the .compozy path on their own. I've now routed all of them through ResolveHomeDir / ResolveHomePaths so COMPOZY_HOME is honored everywhere.

Going through your findings:

On (1), resolveConfigPaths no longer calls os.UserHomeDir and reassembles the path by hand. It uses ResolveHomePaths() directly. I kept globalRoot as filepath.Dir(homePaths.HomeDir) so relative add_dirs still resolve against the same base as before in the default layout, and against something sensible under the override. Global config and the daemon now share one root.

On (2), the enablement store and discovery now treat their home field as the Compozy root (the .compozy or COMPOZY_HOME directory) instead of the OS home with .compozy hardcoded, and an empty value falls back to ResolveHomeDir. The tricky part was the CLI ext flow, where the OS home is genuinely needed for .claude / .codex / .config through setup.ResolverOptions. So I kept the two homes separate: OS home stays for setup, and a new resolveCompozyHome dep drives install, the store, and discovery. Two daemons with different COMPOZY_HOME values now get their own extensions and .compozy-state.json.

On (3), discoverGlobalWorkspaceMarkerDir just calls ResolveHomeDir() now.

On (4), good catch, that claim was wrong. Update state lives under XDG (~/.config/compozy/state.yml), never .compozy. I removed it from the description along with the "no other call sites needed updates" line, which obviously wasn't true.

While I was at it I found one more consumer in the same boat: globalAgentsRoot in internal/core/agents was also building $HOME/.compozy/agents from os.UserHomeDir, so global reusable agents ignored the override too. Routed that one as well.

On your test note, you were right that the guard was too narrow. I broadened it beyond home_test.go: isolateWorkspaceConfigHome now clears any ambient COMPOZY_HOME, and the extension and daemon test stores root at the Compozy home so they line up with how the runtime resolves it.

make fmt, make lint (0 issues), make go-build and make test are green for the touched packages (config, core/workspace, core/extension, core/agents, cli, cli/extension, daemon). Left the legitimate OS home lookups (.claude, .codex, XDG, path display) alone.

@pedronauck
pedronauck merged commit 73ced7f into compozy:main Jul 2, 2026
8 checks passed
This was referenced Jul 2, 2026
pedronauck added a commit that referenced this pull request Jul 3, 2026
Add human-written release notes for the next release covering the work
merged since v0.2.10:

- feat: dependency-aware parallel task execution (--parallel-tasks) (#212)
- feat: agentic recovery for failed runs (--recovery) (#212)
- feat: COMPOZY_HOME for isolated per-project daemons (#216)
- feat: repo-level default overrides in compozy setup (#90)
- fix: safer worktree management for parallel runs (#217, #223)
- fix: ACP runs consistently apply the selected model (#215)
- fix: run TUI elapsed timer restored across all terminal outcomes (#221)

Co-Authored-By: Claude Fable 5 <[email protected]>
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.

3 participants