Skip to content

[CLAUDE ROUTINE]: Reliability enhancement — atomic settings write to prevent concurrent-install clobber #150

Closed

Description

@NiveditJain

Summary

writeSettings() in manager.ts does a classic read-modify-write on settings.json without atomic replace. Two concurrent failproofai policies --install runs (common in CI matrix jobs that install in parallel across shards) can clobber each other. token-store.ts already does this correctly with a temp-file + rename — applying the same pattern here makes installation bulletproof.

Where

  • src/hooks/manager.ts:51-62 — readSettings / writeSettings
  • Good reference: src/auth/token-store.ts:46-56 — writes temp file + renameSync for atomic replacement
// current
function writeSettings(settingsPath: string, settings: ClaudeSettings): void {
  mkdirSync(dirname(settingsPath), { recursive: true });
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
}

Why this helps

In CI environments (GitHub Actions matrix runs, monorepo bootstrap scripts) it's normal for two failproofai policies --install invocations to run concurrently against the same ~/.claude/settings.json. Today:

  1. Process A reads settings v1
  2. Process B reads settings v1
  3. Process A writes v1 + hookA
  4. Process B writes v1 + hookB ← clobbers A's write

Atomic replace using writeFile(tmp) + rename(tmp, final) guarantees that the last write wins cleanly instead of interleaving.

Flow

BEFORE                              AFTER
─────────────                       ─────────────
read  settings.json                 writeFile  settings.json.tmp
mutate in memory                    renameSync settings.json.tmp → settings.json
write settings.json                 (POSIX rename is atomic)
     (last writer clobbers)

Proposed enhancement

function writeSettings(settingsPath: string, settings: ClaudeSettings): void {
  mkdirSync(dirname(settingsPath), { recursive: true });
  const tmp = `${settingsPath}.${process.pid}.tmp`;
  writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
  renameSync(tmp, settingsPath);
}

Pairs nicely with the file-permission enhancement — both land in the same helper.

Acceptance criteria

  • Concurrent install stress test: two failproofai policies --install <different-policy> runs end with both policies present in settings.json.
  • Unit test that interleaves a stubbed read/write pair and asserts no lost mutation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions