Repository navigation
fix(root): stop the _common.sh root search climbing out of a multi-app project (#420) - #487
Merged
Merged
Conversation
…p project (#420) Tier 2 and tier 3 accepted a model only at <root>/ or <root>/app/. A multi-app folder (apps/<X>/X.mpr) matched neither, so tier 3 walked up from $PWD and took the first ancestor holding any .mpr (or fell back to the toolkit), and look-ledger rows landed in another folder's ledger. - project-bin/_common.sh: _mxtk_has_model probes apps/*/ as a third layout; mxtk_walk_root stops at the first folder that holds a model or is a project folder (.claude/ other than $HOME's, or an installed bin/_common.sh). find_mpr finds a single apps/<X>/ model and refuses to guess between several, naming MPR_FILE=apps/<X>/<X>.mpr. - bin/check-requirements.sh: its private root-only copy of the walk is replaced by mxtk_walk_root, so --fix no longer installs tests/e2e into a stray ancestor. - tests/wave2/test-common-root-multiapp.sh: 20 cases over the three layouts with a stray ancestor .mpr, nested multi-app folders, a model-less project and check-requirements; 12 fail on master. Single-tree and two-tree resolution are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Claude Code creates .claude/ in whatever folder it runs in; a stray /tmp/.claude on the test machine stopped the walk there and broke test-coverage-check-shape.sh's self-parent fallback. A project folder is now an installed bin/_common.sh, or .claude/ beside PROJECT.md (the pair init-project.sh writes). Fixture gains R11 (bare .claude/ above an unmarked project: the old fallback and the model above still apply). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…nd metrics-ledger the apps/ layout (#420) find_mpr accepted apps/<X>/ but four consumers still probed root and app/ only: model-stamp.sh check refused a staged model with "no .mpr resolves", lint-gate.sh exited 2 (exec.sh logged lint as "could not run"), doctor.sh reported no model, and metrics-ledger.sh's fallback found none. - model-stamp.sh: _mxtk_has_model (type-guarded, inline fallback for a held-back _common.sh). With several apps and nothing model-related staged `check` still passes, so a multi-app folder does not block every commit; staged files under one apps/<X>/ are checked against that app; staged across several refuse and name MPR_FILE. - lint-gate.sh: one app linted; several refused naming MPR=apps/<X>/<X>.mpr. - doctor.sh: both probes; several apps are listed with the MPR_FILE hint. - metrics-ledger.sh: the no-_common.sh fallback probes apps/*/. - sync-project.sh: known_fix_note for a held-back _common.sh names #420 and the --only-bin _common.sh --upgrade-bin _common.sh remedy. - check-requirements.sh: fail with exit 2 if _common.sh cannot be loaded. - test-common-root-multiapp.sh: R12-R16 (model-stamp, lint-gate, doctor, a model-less worktree under .claude/worktrees/); 33/33, 22 fail on master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Keeps both ## Unreleased CHANGELOG entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
…ames the checked app (#420) Review: the several-apps refusal suggested MPR_FILE=apps/<X>/<X>.mpr, and doing that verified A while B's staged changes went through unverified (the stamp covers one model's paths). - model-stamp.sh: the refusal now says commit one app at a time (git reset apps/<Y>, MPR_FILE=... ./bin/verify-model.sh, commit). In a multi-app folder with MPR_FILE set, `check` refuses when any staged model path is outside dirname(MPR_FILE) and names those paths. - doctor.sh --property-sets: honours MPR_FILE; otherwise says which app it checked and names the others. - test-common-root-multiapp.sh: R13e (A+B staged, MPR_FILE=A: refused; passed unverified at the previous head), R13f (only A staged and stamped: passes), R15c. 36/36. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
#420) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
6 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Direct lane
What changed and why (one paragraph): In a multi-app folder (
apps/<X>/X.mpr), instruments that sourceproject-bin/_common.shresolvedPROJECT_ROOToutside the project. Tier 2 and tier 3 of the root search accepted a model only at<root>/or<root>/app/. The project matched neither, so tier 3 walked up from$PWDand took the first ancestor holding any.mpr; with none above, it fell back to the toolkit. Look-ledgerseenrows then landed in that other folder's.claude/loop/look/. The fix, as the issue proposes:_mxtk_has_modelprobesapps/*/alongside the root andapp/. Tier 2 uses it too.mxtk_walk_rootstops at the first folder that either holds a model or is a project folder: it has an installedbin/_common.sh, or a.claude/beside aPROJECT.md(the pairinit-project.shwrites), and is not$HOME. The toolkit's own copy lives inproject-bin/, so the toolkit is not a project folder..claude/is deliberately not enough. The issue suggested.claude/alone, but Claude Code creates that folder wherever it runs. A stray empty/tmp/.claudeon the test machine stopped the walk at/tmp, which broketest-coverage-check-shape.sh's self-parent fallback (rc 4). The second commit narrows the marker and adds R11 for it.find_mpraccepts the same layout. It returns the model when exactly one app sits underapps/. With several it refuses to guess, as it already did for two root models: it lists them and namesMPR_FILE=apps/<X>/<X>.mpr.bin/check-requirements.shkept a private copy of the old walk that probed the root only, so on a two-tree or multi-app project--fixcould installtests/e2einto a stray ancestor. It now sources_common.shand usesmxtk_walk_root, asdoctor.shalready sources_common.sh.What stays as before:
$PWDinside one app's folder (apps/A/javasource) resolves to that app's folder, as before.MPR_FILEandPROJECT_ROOToverrides.The "multi-app model key" the issue mentions (ledger state keyed per model) is not part of this. With several apps, project-level state stays at the project folder, which is the fix for the misrouting.
Review round (third commit, then a merge of master with #475).
find_mpracceptedapps/<X>/, but four consumers still probed for the model at the root and underapp/only. They now know the multi-app layout too:model-stamp.sh check. A staged model in a multi-app folder no longer exits 1 with "no .mpr resolves". The no-model path is kept, and with several apps nothing staged still passes, so a multi-app folder does not block every commit. Staged files under oneapps/<X>/are checked against that app. Staged files across several apps are refused with "commit one app at a time" (git reset apps/<Y>, thenMPR_FILE=apps/<X>/<X>.mpr ./bin/verify-model.sh, then commit). WithMPR_FILEset in a multi-app folder,checkrefuses when any staged model path lies outsidedirname "$MPR_FILE", and names those paths. At 9dd85db,MPR_FILE=apps/A/A.mprwith A and B staged verified A and let B through unverified, because the stamp covers one app's paths; new case R13e reproduced that on 9dd85db and is refused now. The probe uses_mxtk_has_model, guarded withtype, with an inline fallback for a held-back_common.sh.lint-gate.sh. It exited 2, soexec.shlogged lint as "could not run". It now lints a single app and refuses several, namingMPR=apps/<X>/<X>.mpr.doctor.sh. Both probes were fixed; several apps are listed with theMPR_FILEhint.--property-setshonoursMPR_FILE; otherwise it says which app it checked (the first) and names the others.metrics-ledger.sh. The fallback used when_common.shis missing now probesapps/*/too.Also in this round:
sync-project.sh'sknown_fix_notenames [bug] _common.sh root search climbs to any ancestor .mpr in a multi-app layout, so ledgers land in the wrong folder #420 for a held-back_common.sh.check-requirements.shexits 2 if_common.shcannot be loaded.Fixes #420
Impact
find_mprcallers (exec.sh,snapshot-mpr.sh, …) now stop with "N .mpr files … set MPR_FILE=apps//.mpr". Before, they said "no .mpr found" or, worse, used a stray ancestor model.project-bin/_common.sh(root resolution andfind_mpr; sourced by every installed instrument and the look-ledger hook),project-bin/model-stamp.sh,project-bin/lint-gate.sh,project-bin/metrics-ledger.sh,bin/doctor.sh,bin/check-requirements.shandbin/sync-project.sh(oneknown_fix_noteline). A hand-editedbin/_common.shis held back bysync-project.sh, and so is every older script that depends on it, so those projects do not get #420 from a plain sync. The remedy issync-project.sh <project> --only-bin _common.sh --upgrade-bin _common.sh, which backs up the old copy first; the sync drift warning now names #420 and that command. An unedited copy is refreshed by sync as usual. No new tools:ls/dirnameglobbing as before, bash 3.2 safe. New public helpermxtk_walk_root. No order dependency on #484/#485 apart from the shared## UnreleasedCHANGELOG spot.lsglob (apps/*/*.mpr) and two[ -f/-d ]tests. The walk now usually ends earlier, at the project folder instead of/. Not measurable at script start.Field evidence: the issue's repro, taken verbatim as the fixture's R1/R2: a project at
x/stray/projwithapps/A/A.mprand a stray.mprinx/stray. On master the installed copy and the toolkit copy both resolvex/stray(and, with the stray removed, the toolkit folder). On this branch both resolvex/stray/proj. The #414 PR body records the same tier-3 symptom on a lab-project copy under/tmp, where it had to be worked around with an explicitPROJECT_ROOT. No client project was used.LEAKGUARD_ALLOW_NO_DENYLIST=1 bin/check-no-client-data.shis cleantests/wave2/test-common-root-multiapp.sh project-bin/_common.sh: 36/36. Against master, 24 fail; the 12 that pass on master are the unchanged-behaviour guards. The cases added in the review round:model-stamp.shwith one app and with two, including R13e (A and B staged,MPR_FILE=A: refused) and R13f (only A staged and stamped,MPR_FILE=A: passes).lint-gate.sh.doctor.sh, including R15c (--property-setswith two apps names the app it checked)..claude/worktrees/<w>resolves to the worktree, not the main project. This is a deliberate consequence of the bin/_common.sh marker and is stated in the CHANGELOG._common.shorcheck-requirements.sh: 38/38 pass (first round).test-model-stamp35,test-install-claude-permissions77,test-mx-metadata-note40,test-property-sets61,test-lint-gate-accept20,test-lint-gate-new-findings9,test-lint-receipt-revert55,test-exec-lint-autorestore17,test-exec-lock-watch84,test-sec014-notice24,test-exec-gate-log24,test-doctor-docker-probe18,test-doctor-gate-selftest28,test-doctor-mxcli-arch,test-doctor-stale-hooks7,test-metrics-ledger42,test-exec-change-class21,test-look-ledger11,test-common-windows10,test-coverage-check-shape8/8,test-affected-tests138,test-sync-only-bin40,test-bug12-sync61,test-sync-past-stock22.test-common-root-multiapp36,test-model-stamp35,test-doctor-docker-probe18,test-doctor-gate-selftest28,test-doctor-mxcli-arch,test-doctor-stale-hooks7,test-property-sets61.test-look-ledger-hook-repairfails H7 (a JSON re-format of a"für"value) the same way on an untouchedorigin/masterexport in this environment. It is not caused by this PR.bin/check-scripts.sh,bin/check-portability.sh,bin/render-routing.sh --checkandbin/check-pr-discipline.sh origin/masterare all clean.app/), multi-app (apps/<X>/), nested multi-app folders, and a model-less project, each with a stray ancestor.mpr. Platforms: onlylsglobs,dirname,[ -f/-d ]and a$HOMEstring compare (Git Bash reports$HOMEandpwdin the same/c/Users/...form); bash 3.2 compatible. Producer:.claude/is written byinit-project.sh/mxcli init, andbin/_common.shbyinit-project.sh's install stepCHANGELOG.mdline appended, credited to MendixMauFollow-up, not in this PR (for the coordinator to file). The installed copy and the toolkit copy can disagree when
$PWDis insideapp/orapps/<A>/. The installed copy (<project>/bin/x.sh) resolves through tier 2, the script's own parent, which is the project. The shared toolkit copy resolves through tier 3, which walks up from$PWDand stops at the first folder holding a model; from insideapps/A/that isapps/A, and from insideapp/it isapp. The fixture's R8 pins today's tier-3 answer. Project state (ledgers, stamps) can therefore land in two places depending on which copy ran. A fix needs a decision on whether state follows the model (a per-model key) or the project.https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw
Generated by Claude Code