Skip to content

15 · install-rejects-reinstall

What this scenario proves

archon install refuses to clobber an existing Archon installation unless --force is given. The exact guard is in docs/source-files/tools/archon-cli/lib/install.mjs lines 31–38:

text
if (await pathExists(path.join(targetDir, '.archon', 'soul.md'))) {
  if (!force) {
    throw new Error(
      'This project already has Archon installed (.archon/soul.md exists). Use `archon update` or pass --force to re-install.',
    )
  }
}

A passing run here proves three things:

  1. The second archon install returns non-zero.
  2. No new files are written (no .archon-backup-* directory is created — backups only happen on --force).
  3. The first install's drift ledger is untouched — exactly one ## install row, not two.

This is the sibling negative test of 16 · install-force-reinstall, which exercises the explicit override path.

Test environment

Fixturefixtures/sandbox-node-ts (post first install)
IDECursor
Manifest version under testv0.1.x

Pre-conditions

  1. Fixture copied — runner-managed.
  2. First archon install --with=cli succeeds (prerequisite).
  3. .archon/soul.md exists.

Steps

text
1. Run `archon install --with=cli` a second time.
2. Observe the CLI exits non-zero with the "already has Archon installed"
   error message.

Expected outcome

CheckExpected
Step 1 exit codenon-zero
.archon/soul.mdstill exists, unchanged
.archon/drift.md install rowsexactly 1 (the prerequisite's row)
.archon-backup-* directoryabsent (no force, no backup)

The runner's allow_nonzero: true flag tells the harness to expect the non-zero exit and proceed to assertions instead of marking the scenario as failing.

Demo recordings

Will be replaced by docs/public/videos/install-rejects-reinstall.mp4 once recorded. See videos/README for upload conventions.
Will be replaced by docs/public/asciinema/install-rejects-reinstall.cast embedded via the asciinema-player web component. See asciinema/README for the recording command.

Run records

bash
node scripts/sandbox-run.mjs --only=install-rejects-reinstall
Started (UTC)ManifestRunnerResultDurationNotesRecord
2026-06-08 08:17:12 UTCv0.1.0cli✅ passing360 msJSON
2026-06-07 07:10:17 UTCv0.1.0cli✅ passing370 msJSON
2026-06-06 06:34:41 UTCv0.1.0cli✅ passing383 msJSON
2026-06-05 07:14:07 UTCv0.1.0cli✅ passing367 msJSON
2026-06-04 07:56:44 UTCv0.1.0cli✅ passing374 msJSON
2026-06-03 08:24:18 UTCv0.1.0cli✅ passing382 msJSON
2026-06-02 08:00:52 UTCv0.1.0cli✅ passing383 msJSON
2026-06-01 08:40:46 UTCv0.1.0cli✅ passing382 msJSON
2026-05-31 07:02:24 UTCv0.1.0cli✅ passing375 msJSON
2026-05-30 06:27:54 UTCv0.1.0cli✅ passing365 msJSON
2026-05-29 06:59:40 UTCv0.1.0cli✅ passing370 msJSON
2026-05-28 06:59:34 UTCv0.1.0cli✅ passing357 msJSON
2026-05-27 07:09:42 UTCv0.1.0cli✅ passing361 msJSON
2026-05-26 06:52:09 UTCv0.1.0cli✅ passing332 msJSON
2026-05-25 07:20:22 UTCv0.1.0cli✅ passing359 msJSON
2026-05-24 06:41:53 UTCv0.1.0cli✅ passing368 msJSON
2026-05-23 06:16:50 UTCv0.1.0cli✅ passing362 msJSON
2026-05-22 06:54:18 UTCv0.1.0cli✅ passing353 msJSON
2026-05-21 06:58:05 UTCv0.1.0cli✅ passing355 msJSON
2026-05-20 06:53:38 UTCv0.1.0cli✅ passing347 msJSON
2026-05-19 06:53:18 UTCv0.1.0cli✅ passing361 msJSON
2026-05-18 07:01:47 UTCv0.1.0cli✅ passing381 msJSON
2026-05-17 06:27:37 UTCv0.1.0cli✅ passing375 msJSON
2026-05-16 06:04:41 UTCv0.1.0cli✅ passing355 msJSON
2026-05-15 06:38:00 UTCv0.1.0cli✅ passing373 msJSON
2026-05-14 06:28:57 UTCv0.1.0cli✅ passing375 msJSON
2026-05-13 06:30:25 UTCv0.1.0cli✅ passing329 msJSON
2026-05-12 06:19:01 UTCv0.1.0cli✅ passing364 msJSON
2026-05-11 06:45:35 UTCv0.1.0cli✅ passing346 msJSON
2026-05-10 06:16:45 UTCv0.1.0cli✅ passing392 msJSON
2026-05-09 05:52:40 UTCv0.1.0cli✅ passing389 msJSON
2026-05-08 05:33:36 UTCv0.1.0cli✅ passing387 msJSON
2026-05-07 11:01:03 UTCv0.1.0cli✅ passing353 msJSON
2026-05-07 09:54:54 UTCv0.1.0cli✅ passing368 msJSON
2026-05-07 09:45:13 UTCv0.1.0cli✅ passing539 msJSON
2026-05-07 06:15:45 UTCv0.1.0cli✅ passing293 msJSON
2026-05-06 11:45:37 UTCv0.1.0cli✅ passing354 msJSON
2026-05-06 11:44:06 UTCv0.1.0cli✅ passing342 msJSON
2026-05-06 11:19:27 UTCv0.1.0cli✅ passing356 msJSON

Known limitations

  • Does not assert the exact error text. If the wording in install.mjs changes, this scenario stays green; only the exit code is checked. A future enhancement could capture stderr and file_matches it (current runner doesn't expose stderr to the assertion phase).
  • Does not test the --force half — that lives in scenario 16.

Cross-references

json
{
  "runnable": "cli",
  "fixture": "fixtures/sandbox-node-ts",
  "ide_platform": "cursor",
  "prerequisites": [
    {
      "name": "drop sandbox check helper",
      "write_file": {
        "path": "__sb-check.cjs",
        "content": "const fs=require('fs');const op=process.argv[2];const args=process.argv.slice(3);if(op==='drift-install-count'){const want=Number(args[0]);const c=fs.readFileSync('.archon/drift.md','utf8');const m=c.match(/^## install /gm);const n=m?m.length:0;if(n!==want){console.error('drift install rows: want',want,'got',n);process.exit(1)}process.exit(0)}if(op==='no-backup-dir'){const found=fs.readdirSync('.').some(function(x){return x.indexOf('.archon-backup-')===0});if(found){console.error('unexpected .archon-backup-*');process.exit(1)}process.exit(0)}if(op==='has-backup-dir'){const found=fs.readdirSync('.').some(function(x){return x.indexOf('.archon-backup-')===0});if(!found){console.error('expected .archon-backup-* missing');process.exit(1)}process.exit(0)}if(op==='pkg-name-equals'){const p=require('./package.json');if(p.name!==args[0]){console.error('package.json name mutated:',p.name);process.exit(1)}process.exit(0)}if(op==='file-includes'){const text=fs.readFileSync(args[0],'utf8');if(text.indexOf(args[1])===-1){console.error(args[0],'missing',args[1]);process.exit(1)}process.exit(0)}if(op==='drift-modules-excludes'){const c=fs.readFileSync('.archon/drift.md','utf8');const re=new RegExp('^- Modules:.*\\\\b'+args[0]+'\\\\b','m');if(re.test(c)){console.error(args[0],'unexpectedly listed');process.exit(1)}process.exit(0)}console.error('unknown op:',op);process.exit(2);\n"
      }
    },
    {
      "name": "first archon install",
      "cli": "install",
      "flags": ["--with=cli"]
    }
  ],
  "steps": [
    {
      "name": "second archon install (expected to refuse)",
      "cli": "install",
      "flags": ["--with=cli"],
      "allow_nonzero": true
    }
  ],
  "assertions": [
    { "file_exists": ".archon/soul.md" },
    { "file_exists": ".archon/drift.md" },
    { "cmd_zero": ["node", "__sb-check.cjs", "drift-install-count", "1"] },
    { "cmd_zero": ["node", "__sb-check.cjs", "no-backup-dir"] },
    { "cmd_zero": ["python3", "scripts/archon-check.py", "--root", "."] }
  ],
  "notes": "Negative test: the second install must be rejected with non-zero exit, no backup, no drift mutation."
}

Released under the Apache-2.0 License.