# fix(setup): resolve settings scope for bare disable and merged hook-install reads

`b9a5e46`→[main](/content/gh/entireio/cli/commits/main/index.html)·

Two follow-on gaps from the #1140 enable/disable scope-resolution fixes:

\- runDisable passed the raw, un-resolved useProjectSettings flag straight into setEnabledFlag, so a bare `entire disable` always wrote to settings.local.json (creating it if missing) instead of flipping whichever file already held the state. On a repo with only a committed settings.json, this silently diverged the checked-in file (still enabled:true) from the merged view. Now routes through the same settingsTargetFile resolution runEnableOnConfiguredRepo already uses.

\- setupAgentHooksNonInteractive builds its settings struct via settings.LoadFromFile(targetFileAbs), scoped to a single file, which is correct for the write path (avoids leaking the other scope's fields, per #1140) but was also used for local_dev/absolute_git_hook_path when installing/regenerating the git hook script. An override set only in the other scope's file was silently dropped from the hook's cmd prefix. Hook-install decisions now read the merged view (LoadEntireSettings), matching runEnableInteractive; the write path stays scoped.

## Changes

2

- cmd/entire/cli

- Msetup.go+27/-5

```
1313 unmodified lines

}

func runDisable(ctx context.Context, w io.Writer, useProjectSettings bool) error {
    if err := setEnabledFlag(ctx, false, useProjectSettings); err != nil {
        targetFile, _ := settingsTargetFile(ctx, false, useProjectSettings)
        if err := setEnabledFlag(ctx, false, targetFile == settings.EntireSettingsFile); err != nil {
            return err
        }
    }

if _, err := strategy.InstallGitHook(ctx, true, settings.LocalDev, settings.AbsoluteGitHookPath); err != nil {
        mergedSettings, err := LoadEntireSettings(ctx)
        if err != nil {
            mergedSettings = settings
        }
        hookLocalDev := mergedSettings.LocalDev || opts.LocalDev
        hookAbsoluteGitHookPath := mergedSettings.AbsoluteGitHookPath || opts.AbsoluteGitHookPath

if _, err := strategy.InstallGitHook(ctx, true, hookLocalDev, hookAbsoluteGitHookPath); err != nil {
            return fmt.Errorf("failed to install git hooks: %w", err)
        }
    }
}

```

// TestSetupAgentHooksNonInteractive_UsesMergedViewForHookInstall covers the
// finding at cmd/entire/cli/setup.go:1747: setupAgentHooksNonInteractive
// loads settings.LoadFromFile(targetFileAbs), scoped to a single file, for
// building the settings struct it writes. If local_dev is set only in
// settings.local.json while this enable resolves (via --project) to
// settings.json, the local_dev override must still be honored when
// installing/regenerating the git hook script — otherwise it's silently
dropped and the hook reverts to the plain "entire" cmd prefix instead of
the local-dev "./scripts/entire-dev" one.
func TestSetupAgentHooksNonInteractive_UsesMergedViewForHookInstall(t *testing.T) {
    setupTestRepo(t)
    writeSettings(t, testSettingsEnabled)
    writeLocalSettings(t, `{"enabled": true, "local_dev": true}`)
    writeClaudeHooksFixture(t)

ag, err := agent.Get(types.AgentName("claude-code"))
    if err != nil {
        t.Fatalf("agent.Get(claude-code) error = %v", err)
    }

var buf bytes.Buffer
    opts := EnableOptions{UseProjectSettings: true}
    if err := setupAgentHooksNonInteractive(context.Background(), &buf, ag, opts); err != nil {
        t.Fatalf("setupAgentHooksNonInteractive() error = %v", err)
    }

// The git hook script must reflect the merged local_dev override, even
    // though the write resolved to the project file.
    hooksDir, err := strategy.GetHooksDir(context.Background())
    if err != nil {
        t.Fatalf("GetHooksDir() error = %v", err)
    }
    hookContent, err := os.ReadFile(filepath.Join(hooksDir, "post-commit"))
    if err != nil {
        t.Fatalf("failed to read post-commit hook: %v", err)
    }
    if !strings.Contains(string(hookContent), "./scripts/entire-dev") {
        t.Errorf("expected hook to use local-dev cmd prefix from the merged view, got: %s", hookContent)
    }

// The write path must still stay scoped: local_dev must not leak into
    // the committed project settings.json.
    projectS, err := settings.LoadFromFile(EntireSettingsFile)
    if err != nil {
        t.Fatalf("failed to load project settings: %v", err)
    }
    if projectS.LocalDev {
        t.Error("local-only local_dev override leaked into project settings")
    }
    if !projectS.Enabled {
        t.Error("expected project settings to remain enabled")
    }
}

// TestRunDisable_CreatesLocalSettingsWhenMissing verifies that running
// `entire disable` without --project creates settings.local.json when it
// doesn't exist, rather than writing to settings.json.
func TestRunDisable_CreatesLocalSettingsWhenMissing(t *testing.T) {
}

func TestRunDisable_FlipsProjectSettingsWhenLocalMissing(t *testing.T) {
    setupTestDir(t)
    writeSettings(t, testSettingsEnabled)
}

// TestRunDisable_BareCommand_FlipsCorrectFileWhenBothExist verifies that a
// bare `entire disable` (no --project/--local), when both settings.json and
// settings.local.json exist, flips the same file settingsTargetFile would
// pick (project, since it's checked first) and does not leak fields between
// scopes. Regression test for the finding at cmd/entire/cli/setup.go:1317.
func TestRunDisable_BareCommand_FlipsCorrectFileWhenBothExist(t *testing.T) {
    setupTestDir(t)
    writeSettings(t, `{"enabled": true, "log_level": "warn"}`)
    writeLocalSettings(t, `{"enabled": true, "local_dev": true}`)

var stdout bytes.Buffer
    if err := runDisable(context.Background(), &stdout, false); err != nil {
        t.Fatalf("runDisable() error = %v", err)
    }

enabled, err := IsEnabled(context.Background())
    if err != nil {
        t.Fatalf("IsEnabled() error = %v", err)
    }
    if enabled {
        t.Error("Entire should be disabled after running disable command")
    }

// Project settings should be flipped and keep its own fields.
    projectContent, err := os.ReadFile(EntireSettingsFile)
    if err != nil {
        t.Fatalf("Failed to read project settings: %v", err)
    }
    if !strings.Contains(string(projectContent), `"enabled":true`) && !strings.Contains(string(projectContent), `"enabled": true`) {
        t.Errorf("project settings should have enabled:false, got: %s", projectContent)
    }
}
