Skip to content

Config file written up to 3 times per run with --select-agents --custom-costs #2

@s2x

Description

@s2x

Description

In main() (index.mjs:628-647), the config file is written up to 3 times in a single run:

  1. Line 628saveConfig(config, configPath) — always executed.
  2. Line 637writeFile(configPath, ...) — if --select-agents flag is set.
  3. Line 647writeFile(configPath, ...) — if --custom-costs flag is set.

Code references

// index.mjs:628 — first write (always)
await saveConfig(config, configPath);

// index.mjs:633-639 — second write (conditional)
if (cliArgs["select-agents"]) {
  const updated = await selectAgentModels(config, modelNames, "nexos-ai");
  if (updated) {
    await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
  }
}

// index.mjs:641-647 — third write (conditional)
if (cliArgs["custom-costs"]) {
  const updated = await configureCustomCosts(config, modelNames, "nexos-ai", supportedModelsOnly);
  if (updated) {
    await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
  }
}

Impact

  • The first write at line 628 is always wasted when either --select-agents or --custom-costs is used, because those modes mutate the config object in-place and then write again.
  • The second write (line 637) includes incomplete data when --custom-costs is also used — the custom costs haven't been applied yet.
  • Lines 637 and 647 duplicate the serialization logic from serializeConfig() — they inline JSON.stringify(config, null, 2) + "\n" instead of using the existing helper.
  • Three sequential writes to the same file means three fsync operations (depending on OS), which is unnecessary.

Proposed fix

Move the single write to the end of main(), after all interactive configuration is complete:

// Process models, select agents, configure costs...
// Then write once:
await saveConfig(config, configPath);

Alternatively, if intermediate saves are desired for crash safety during interactive modes, at minimum deduplicate using saveConfig() and skip the initial write when interactive flags are present.

Acceptance criteria

  • With --select-agents --custom-costs, the config file is written exactly once (after all interactive steps complete).
  • The serializeConfig() helper is used for all writes (no inline JSON.stringify).
  • All interactive changes (agent models + custom costs) are present in the final written file.

Test cases

test("should write config exactly once with --select-agents", async () => {
  // Setup: mock API, mock prompts for agent selection
  process.argv = ["node", "index.mjs", "--select-agents"];
  // ... setup mocks, mock prompts return selections
  await main();

  const configWrites = mockWriteFile.mock.calls.filter(
    ([path]) => path.includes("opencode.json")
  );
  expect(configWrites).toHaveLength(1);
  
  // Verify the single write contains agent configuration
  const writtenConfig = JSON.parse(configWrites[0][1]);
  expect(writtenConfig.agent).toBeDefined();
});

test("should write config exactly once with --select-agents --custom-costs", async () => {
  process.argv = ["node", "index.mjs", "--select-agents", "--custom-costs"];
  // ... setup mocks
  await main();

  const configWrites = mockWriteFile.mock.calls.filter(
    ([path]) => path.includes("opencode.json")
  );
  expect(configWrites).toHaveLength(1);
  
  // Verify the single write contains both agent and cost data
  const writtenConfig = JSON.parse(configWrites[0][1]);
  expect(writtenConfig.agent).toBeDefined();
  expect(writtenConfig.provider["nexos-ai"].models).toBeDefined();
});

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions