Found by CodeRabbit on the cs-toolkit adoption (in-parallel-oy/cs-toolkit#1791), and
confirmed by execution.
The claim
scripts/lib/kitconfig.py:53
def repo_root(start: Path | None = None) -> Path:
"""Nearest ancestor carrying a ``.git`` entry ... Walking up for the marker —
rather than counting ``parents[N]`` — is what lets the kit be vendored at any
depth (``scripts/devkit/lib/``) without rewriting a single path."""
here = (start or Path(__file__)).resolve()
for candidate in (here, *here.parents):
if (candidate / ".git").exists():
return candidate
return here.parents[2] if len(here.parents) >= 3 else here.parent
The docstring names scripts/devkit/lib/ as the layout this supports. The
fallback then does exactly what the docstring says it avoids — counts
parents[N] — and [2] is calibrated for the kit's own scripts/lib/ layout:
| layout |
parents[2] |
correct? |
scripts/lib/kitconfig.py (kit's own) |
<repo> |
✓ |
scripts/devkit/lib/kitconfig.py (vendored, and the one the docstring names) |
<repo>/scripts |
✗ |
Reproduction
$ mkdir -p /tmp/v1/repo/scripts/devkit/lib # deliberately NO .git
$ cp scripts/lib/kitconfig.py /tmp/v1/repo/scripts/devkit/lib/
$ cd /tmp/v1/repo/scripts/devkit/lib && python3 -c "
import kitconfig, pathlib
print('returned :', kitconfig.repo_root())
print('expected :', pathlib.Path('/tmp/v1/repo').resolve())"
returned : /tmp/v1/repo/scripts
expected : /tmp/v1/repo
load_config() then looks for <repo>/scripts/config/dev-model.yaml and raises
FileNotFoundError with a hint pointing at the wrong path.
Scope
Latent, not live, for a normal checkout — the .git walk succeeds first, so the
fallback only fires where there is no .git marker at all: exported tarballs,
GIT_DIR-only setups, some git worktree edge cases. But it is precisely the
vendored-at-depth case the kit now actively recommends: /adopt Step 1 says to
vendor under scripts/devkit/ when the adopter's scripts/ has colliding names,
and cs-toolkit is the first real repo to take that branch.
Suggested fix
Probe for a second marker before falling back to arithmetic:
for candidate in (here, *here.parents):
if (candidate / ".git").exists():
return candidate
- return here.parents[2] if len(here.parents) >= 3 else here.parent
+ for candidate in (here, *here.parents):
+ if (candidate / DEFAULT_CONFIG_PATH).is_file():
+ return candidate
+ return here.parents[2] if len(here.parents) >= 3 else here.parent
This restores the "vendored at any depth" property for the fallback path too, and
removes the last parents[N] count from the function.
Worth a test that runs repo_root() from a scripts/devkit/lib/ tree with no
.git — there is currently no coverage of the fallback branch at any depth, which
is why a docstring and its own code could disagree this plainly.
Found on: in-parallel-oy/cs-toolkit#1791
Found by CodeRabbit on the cs-toolkit adoption (in-parallel-oy/cs-toolkit#1791), and
confirmed by execution.
The claim
scripts/lib/kitconfig.py:53The docstring names
scripts/devkit/lib/as the layout this supports. Thefallback then does exactly what the docstring says it avoids — counts
parents[N]— and[2]is calibrated for the kit's ownscripts/lib/layout:parents[2]scripts/lib/kitconfig.py(kit's own)<repo>scripts/devkit/lib/kitconfig.py(vendored, and the one the docstring names)<repo>/scriptsReproduction
load_config()then looks for<repo>/scripts/config/dev-model.yamland raisesFileNotFoundErrorwith a hint pointing at the wrong path.Scope
Latent, not live, for a normal checkout — the
.gitwalk succeeds first, so thefallback only fires where there is no
.gitmarker at all: exported tarballs,GIT_DIR-only setups, somegit worktreeedge cases. But it is precisely thevendored-at-depth case the kit now actively recommends:
/adoptStep 1 says tovendor under
scripts/devkit/when the adopter'sscripts/has colliding names,and cs-toolkit is the first real repo to take that branch.
Suggested fix
Probe for a second marker before falling back to arithmetic:
for candidate in (here, *here.parents): if (candidate / ".git").exists(): return candidate - return here.parents[2] if len(here.parents) >= 3 else here.parent + for candidate in (here, *here.parents): + if (candidate / DEFAULT_CONFIG_PATH).is_file(): + return candidate + return here.parents[2] if len(here.parents) >= 3 else here.parentThis restores the "vendored at any depth" property for the fallback path too, and
removes the last
parents[N]count from the function.Worth a test that runs
repo_root()from ascripts/devkit/lib/tree with no.git— there is currently no coverage of the fallback branch at any depth, whichis why a docstring and its own code could disagree this plainly.
Found on: in-parallel-oy/cs-toolkit#1791