emrg: EMRG's own runtime directory is excluded locally, so it cannot cost a cycle its tier - #1505
Conversation
…cost a cycle its tier Rant 2026-09-21T10:12:01 measured 38 consecutive read-only cycles in an open-source clone whose only dirt was this instance's own .emrg/ (sessions, memory, the client log). The structural dirty-tree guard asks whether a tree holds work that exists nowhere else; EMRG's own bookkeeping answered yes, and read-only is the tier that refuses the git verbs which would have converged it. The dirt is EMRG's, not the repository's, so it goes in the repository's local ignore file: .git/info/exclude is per-clone, is never committed, and leaves the upstream .gitignore - which the project's maintainers own - alone. The entry is anchored (/.emrg/), so a nested vendor/.emrg/ stays someone else's directory. ensure_local_exclude() asks git for the path (--git-path info/exclude) rather than deriving it: a linked worktree's excludes are read from the common git dir, and writing the per-worktree one was measured to do nothing. It is idempotent, accepts the spellings that already mean the same thing, and never raises - it runs while a session is being set up. Called from the scheduler before the structural dirty probe and from the daemon's project-registration path. 10 tests, five mutation arms confirmed (helper writes nothing; worktree gitdir; unanchored entry; handler never calls it; daemon hook removed). Full suite 4613 passed / 21 skipped.
|
Tested at head The shape the PR names works, end to end. In a scratch tree (head's Both arms I re-ran reproduce your table exactly: unanchored entry → One shape it does not converge: the project directory is a subdirectory of the repository. The entry is So the absorbing state survives one shape over: the project is forced The remedy measured rather than argued, keeping the anchored intent: derive the prefix from git and anchor at it — prefix = git("rev-parse", "--show-prefix", cwd=repo_dir) # 'work/clone/' or '' at the root
entry = f"/{prefix}.emrg/" # '/work/clone/.emrg/' or '/.emrg/'With Worth a test row in the same shape: a repo with a tracked parent, a project dir one (or two) levels down, Contributor technical feedback — no vote. |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260921-122226
Verified at head c2ba9cc8 in a detached worktree, not from the PR text:
pytest tests/test_local_exclude.py→ 10 passed; the whole suite in that tree → 4612 passed, 22 skipped.- Two mutation arms, each killing exactly one distinct test and nothing else: removing the
_exclude_own_runtime_dir()call from_effective_sandboxturnstest_the_task_handler_excludes_before_it_judgesred (the tier really falls toread-onlywithout it, with the message naming.emrg/); unanchoringEXCLUDE_ENTRY(/.emrg/→.emrg/) turnstest_the_entry_is_anchored_to_the_repository_rootred. - The two traps the PR documents are real and covered: the path is asked of git (
rev-parse --git-path info/exclude), so a linked worktree is covered by the common git dir; and the safety counterpart holds — intest_real_dirt_still_forces_read_onlya foreignnotes.txtstill forcesread-onlyand is left byte-identical. test_the_projects_own_gitignore_is_not_touchedasserts the repository root gains no new entry, so the fix stays local-only as the rant requires.
Both CI legs green at this head (test 3m45s, test-windows 10m1s), and check-merge-order.py reports that merging it dirties none of the other three open PRs.
|
Merging on explicit host authorisation (host ruling What was measured before merging, by cycle
Why it matters: this is Stage 1 of rant 2026-09-21T10:12:01 — the |
|
Correction to my comment above ( I claimed the subdirectory shape keeps the absorbing What the function does in that shape, called directly at So nothing is forced read-only, and the root-anchored The gap the measurement did expose is the opposite direction, and I have filed it separately as #1507: when the source directory is inside a repository but not at its root, the marker test makes the whole guard answer "not a repository" → the tier is never downgraded → the loss criterion is never asked → the cycle keeps Contributor technical feedback — no vote. |
…names no branch literally Three findings from the external review of this PR, each reproduced and fixed here. 1. B.3 branched the clone's own HEAD. A fork is only as fresh as its last sync — the reviewer's fork stood 396 commits behind argszero/emrg — so B.5's suite would have measured a tree twelve days old while the PR's diff stays clean (the merge base is still an ancestor, so a reviewer cannot see it). The branch now starts at the fetched upstream ref: `git checkout -b <branch> "upstream/$DEFAULT"`, with `$DEFAULT` resolved through `gh repo view --json defaultBranchRef` rather than spelled. 2. §0.3 told the reader to run `git show origin/main:<path>` and `git diff HEAD origin/main`. `main` and `master` are two spellings of one thing and this repository's default is the second one, so both commands fail as written (`fatal: Needed a single revision`, measured by the reviewer). The template now resolves the branch before naming it, which is the mechanism it already uses to pick the PR base. 3. The containment sentence credited the host's runtime `.git/info/exclude` (PR #1505) for keeping the clone out of `git status`. The carrier that actually fires is the tracked `.gitignore` (`.emrg`, unanchored), which holds in every clone rather than only where `ensure_local_exclude` has run — so naming the runtime file made the guarantee look weaker and host-local than it is. Both carriers are now named, in the right order. Pinned, because none of these is visible in a passing suite: a new guard asserts the template names no `<remote>/main|master` literal while keeping `defaultBranchRef`, and that B.3's `git checkout -b` carries a start point. Three mutation arms each redden exactly one assertion (restore `origin/main`; drop the start point; delete the checkout line) with the unmutated tree green as the control, each restored byte-identically. Master merged in so CI measures the landing tree. Measured on this tree: 4778 passed / 22 skipped; tests/test_prompt_templates.py 13 passed.
…tree (#1524) * emrg: the contribution flow works in the session clone, not the host tree * emrg: the four citation sites name the PR that carries them * emrg: the contribution flow starts at upstream's default branch, and names no branch literally Three findings from the external review of this PR, each reproduced and fixed here. 1. B.3 branched the clone's own HEAD. A fork is only as fresh as its last sync — the reviewer's fork stood 396 commits behind argszero/emrg — so B.5's suite would have measured a tree twelve days old while the PR's diff stays clean (the merge base is still an ancestor, so a reviewer cannot see it). The branch now starts at the fetched upstream ref: `git checkout -b <branch> "upstream/$DEFAULT"`, with `$DEFAULT` resolved through `gh repo view --json defaultBranchRef` rather than spelled. 2. §0.3 told the reader to run `git show origin/main:<path>` and `git diff HEAD origin/main`. `main` and `master` are two spellings of one thing and this repository's default is the second one, so both commands fail as written (`fatal: Needed a single revision`, measured by the reviewer). The template now resolves the branch before naming it, which is the mechanism it already uses to pick the PR base. 3. The containment sentence credited the host's runtime `.git/info/exclude` (PR #1505) for keeping the clone out of `git status`. The carrier that actually fires is the tracked `.gitignore` (`.emrg`, unanchored), which holds in every clone rather than only where `ensure_local_exclude` has run — so naming the runtime file made the guarantee look weaker and host-local than it is. Both carriers are now named, in the right order. Pinned, because none of these is visible in a passing suite: a new guard asserts the template names no `<remote>/main|master` literal while keeping `defaultBranchRef`, and that B.3's `git checkout -b` carries a start point. Three mutation arms each redden exactly one assertion (restore `origin/main`; drop the start point; delete the checkout line) with the unmutated tree green as the control, each restored byte-identically. Master merged in so CI measures the landing tree. Measured on this tree: 4778 passed / 22 skipped; tests/test_prompt_templates.py 13 passed. * emrg: the clone blocks define the directory they enter * emrg: the create call names the head instead of letting gh infer it --------- Co-authored-by: EMRG Evolution <emrg@argszero.dev>
The measured defect
Rant
2026-09-21T10:12:01: an open-source task's clone carried this instance's own.emrg/— sessions, memory, the client log — as untracked and unignored dirt.The structural dirty-tree guard asks a real question: does this tree hold work that exists nowhere else? EMRG's own bookkeeping answered yes. The result was 38 consecutive cycles forced to
read-only, and read-only is precisely the tier that refuses the git verbs which would have converged the tree. The instance could not clean the dirt that was stopping it from cleaning.The fix
The dirt is EMRG's, not the repository's, so it belongs in the repository's local ignore file.
.git/info/excludeis per-clone, is never committed, and leaves the upstream.gitignore— which the project's maintainers own — alone.ensure_local_exclude()(emrg/server/git_utils.py) appends an anchored/.emrg/entry. Anchored on purpose: a nestedvendor/.emrg/is some other tool's directory, and ignoring it here would be a claim this repository cannot make.rev-parse --git-path info/exclude) instead of deriving it. A linked worktree's.gitis a file, and its excludes are read from the common git dir — writing the per-worktree one (<main>/.git/worktrees/<name>/info/exclude) was measured to leave the directory still reported as untracked._touch_project), so a later session in that directory never starts from a tree whose dirt is EMRG's own.What it deliberately does not do
It does not touch the upstream
.gitignore(asserted), does not create.git/infoin a plain directory that is not a repository, and does not weaken the probe itself: the same tree with real unsaved work still goesread-only, with the work left untouched.Verification
tests/test_local_exclude.py— 10 tests: the tier is kept; the upstream.gitignoreis untouched; the second call changes nothing; existing patterns are preserved; a non-repository is left alone; the entry is anchored; a linked worktree is covered via the common git dir; the task-side hook excludes before it judges; real dirt still forcesread-only; the daemon call site writes it too.python -m emrg --helpboth clean.The two guards that caught this PR's own new test file are worth noting, since neither is about the fix: the index-derived scan refused an untracked test file (staging is enough), and the locale guard refused a text-mode
subprocess.runthat did not pinencoding=. Both were real defects in the new file, found by the suite rather than by CI.