Skip to content

fix(scripts): force base 10 in get_next_project_number (#762) - #764

Merged
tractorjuice merged 1 commit into
mainfrom
fix/project-number-octal-762
Aug 13, 2026
Merged

tractorjuice merged 1 commit into
mainfrom
fix/project-number-octal-762

Conversation

@tractorjuice

Copy link
Copy Markdown
Owner

Fixes #762.

The bug

get_next_project_number in both common.sh copies compared the zero-padded directory prefix inside an arithmetic context, where bash reads a leading 0 as octal. 008 and 009 are invalid octal, so the comparison errored and was skipped; 010 and 011 were silently read as decimal 8 and 9. max_num never reached the true maximum, so from the eighth project onward the function returned a number that already existed.

Reproduced exactly as reported, in a repo with 000-global plus 001–011:

common.sh: line 60: ((: 008: value too great for base (error token is "008")
common.sh: line 60: ((: 009: value too great for base (error token is "009")
[INFO] Project number: 010
[SUCCESS] Project created successfully
{ "success": true, "project_number": "010", ... }

projects/010-p010 already existed. Result: two 010- directories, a README and a full set of ARC-010-* artefact paths under the wrong project ID, exit 0.

The failing ((...)) sits in an if condition, where errexit is suspended, so set -euo pipefail did not catch it and create_project_dir's bare mkdir -p did not treat the existing target as an error. This is the second distinct way this same function has failed quietly under set -e, after the find interaction documented at create-project.sh:70.

Verified impact, matching the report row for row:

Existing Returned Expected
001–007 008 008 (correct)
001–008 008 009
001–009 008 010
001–010 009 011
001–011 010 012
001–012 011 013

Blast radius

Larger than the report states. 48 commands invoke scripts/bash/create-project.sh, and all six extensions that ship bash scripts (codex, gemini, opencode, copilot, vibe, kimi) carried the same copy, so the broken path was the default one. The Python implementation in scripts/python/common.py and the JS nextSequence in generate-document-id.mjs were both always correct, which meant the two script sets disagreed on any repo past seven projects.

The fix

Force base 10 at the capture site in both copies, as suggested in the issue. Extension copies are converter output and were regenerated.

Tests

New tests/plugin/test_project_numbering.py:

  • allocation across both bash copies for seven through twelve projects
  • allocation is silent (the original printed two stderr lines and carried on, so a wrong-but-quiet result was the only user-visible signal)
  • bash and Python implementations return the same answer
  • end-to-end: create-project.sh lands on a fresh directory and leaves no duplicate prefix

Seven projects is covered deliberately: it is the last count the octal arithmetic got right, so a future rewrite cannot fix the tail by breaking the head.

Confirmed RED before the fix (24 failed, 4 passed, the 4 being the 007 cases), GREEN after (28 passed). Full suite: 1325 passed / 225 skipped, plus 285 node tests, sync-shared-assets.py --check clean, markdownlint clean.

Not included

Two items deliberately left out of this PR, both worth their own issue:

  1. The suggested hardening. Making create_project_dir refuse an existing target is sound and safe (I checked: create-project.sh has no --number, no reuse path, and only ever creates), but it is a behaviour change beyond the reported defect.
  2. Pre-existing drift between the two common.sh copies, unrelated to this bug. find_repo_root looks for .arckit/ in the root copy and projects/ in the plugin copy, and slugify diverges on accented input: "Café Modernisation" becomes café-modernisation under one copy and caf-modernisation under the other. tests/plugin/test_repo_audit.py enforces byte-identity for the two create-project.sh copies but there is no equivalent guard for common.sh, and one cannot be added while these differences stand.

Thanks to @chrismckelt for a report that pinned the mechanism, the trigger threshold and the fix precisely.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VKbHCujejpxkgvh47BndDE

`get_next_project_number` compared a zero-padded directory prefix inside
an arithmetic context, where bash reads a leading 0 as octal. 008 and 009
are invalid octal, so the comparison errored and was skipped; 010 and 011
were silently read as decimal 8 and 9. max_num never reached the true
maximum, so from the eighth project onward the function returned a number
that already existed.

The failing ((...)) sits in an `if` condition, where errexit is
suspended, so create-project.sh printed two stderr lines, created a
second 010- project over the top of an existing one via `mkdir -p`, and
still reported "success": true with exit 0.

48 commands invoke scripts/bash/create-project.sh and all six extensions
that ship bash scripts carried the same copy, so this was the default
path. The Python implementation was always correct, which meant the two
script sets disagreed on any repo past seven projects.

Adds tests/plugin/test_project_numbering.py: allocation across both bash
copies for seven through twelve projects, silence on stderr, bash/Python
parity, and the reported collision end-to-end. Seven is covered
deliberately as the last count the octal arithmetic got right.

Reported-by: @chrismckelt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VKbHCujejpxkgvh47BndDE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

create-project.sh returns an already-used project number once a repo has 8+ projects (octal parsing in get_next_project_number)

1 participant