Skip to content

claude-settings.test.ts: installStatusLine tests run against the real home directory and can destroy a user's configs #716

Closed

Description

@axisrow

Summary

claude-settings.test.ts runs installStatusLine against the real home directory: one test resets the ccstatusline config path to the actual ~/.config/ccstatusline/settings.json, and another test deletes CLAUDE_CONFIG_DIR without restoring it, so the rest of the file (including every install test) operates on the real ~/.claude/settings.json. When config validation is broken — e.g. under a mutation-testing run, or any regression in the settings parser — these tests silently destroy a developer's real configs.

I just lost both of mine to this while running Stryker over config.ts / claude-settings.ts (details below).

The code (v2.2.32, src/utils/__tests__/claude-settings.test.ts)

  1. Line ~189: this test deletes process.env.CLAUDE_CONFIG_DIR and the afterEach never restores it (restoreAllMocks, initConfigPath(), rmSync — no env restore):
it('should resolve .claude.json beside the default Claude config dir when CLAUDE_CONFIG_DIR is unset', () => {
    delete process.env.CLAUDE_CONFIG_DIR;
    ...
});
  1. Line ~203: this test calls config.initConfigPath() with no argument — that resets the ccstatusline config path to the real ~/.config/ccstatusline/settings.json — and then runs a real install:
it('should use base command when no custom config path', async () => {
    config.initConfigPath();
    await installStatusLine({ commandMode: 'auto-npx' });
    expect(readInstalledCommand()).toBe(CCSTATUSLINE_COMMANDS.NPM);
});

installStatusLine at this point runs with the real ~/.claude/settings.json (see 1) and the real ccstatusline config path (see 2). The same pattern repeats at lines ~333 and ~339.

What actually happened on my machine

Running Stryker with --mutate src/utils/config.ts,src/utils/claude-settings.ts,src/utils/powerline.ts:

  • A mutant in config validation made loadSettings reject my real (rich, heavily customized) ~/.config/ccstatusline/settings.json → the code path materialised default settings over it. The file had been maintained through the TUI for weeks; no backup survived, because the .bak written by a later test in the same run had already captured the destroyed state (each install test backs up whatever the previous one left).
  • Another install test wrote statusLine.command = sh <stryker sandbox>/client/ccstatusline-ipc into my real ~/.claude/settings.json. After the run Stryker deletes .stryker-tmp/, so the referenced file was gone — my Claude Code status line rendered empty until I noticed and reinstalled.

Both files are user-owned state holding real data (hooks, env, apiKeyHelper, a hand-tuned status line). The tests pass green while doing this; nothing in the suite notices.

Why plain bun test is at risk too

On clean code the config is valid, so loadSettings succeeds and the ccstatusline file is "only" read. But the ~/.claude/settings.json overwrite happens on every run: any developer (or CI job) with a personal statusLine, hooks or env in that file has it replaced by a bare {"statusLine": …} written from the test's own CLAUDE_CONFIG_DIR-less state — the .bak/.orig backups then contain whatever earlier tests in the same process already wrote.

Suggested fix

Isolate the whole file the way #653 did for the config tests:

  • in beforeEach, point both HOME and CLAUDE_CONFIG_DIR at a fresh temp directory (and restore both in afterEach, including after the delete in the path-resolution test);
  • never call config.initConfigPath() without a temp path in a test that executes installs;
  • optionally assert at the end of the suite that nothing outside the temp dir was touched.

For anyone running mutation testing over these files in the meantime, a global-setup that redirects HOME/CLAUDE_CONFIG_DIR for the whole sandbox run (like Stryker's globalSetup) is a working band-aid.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions