Skip to content

fix(evidence): decide the dead combined-haystack binding — it stays removed, and why - #66

Merged
stranske merged 4 commits into
mainfrom
claude/wizardly-bohr-4d6622
Aug 23, 2026
Merged

fix(evidence): decide the dead combined-haystack binding — it stays removed, and why#66
stranske merged 4 commits into
mainfrom
claude/wizardly-bohr-4d6622

Conversation

@stranske

@stranske stranske commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Decides a question a lint pass left open, rather than answering it silently.

_dedupe_candidate built haystack = f"#{issue_number} {issue_title}" and then searched
issue_title and candidate["seed"] only — the combined form was constructed and discarded.
ruff F841 flagged the unused local. Removing it as lint is a no-op; the open question was whether
matching against the combined form had been the intent. keepalive_evidence feeds run/outcome
evidence, so changing what this path matches is a change to Brain input (CLAUDE.md §2) and does
not belong in a lint pass.

Determination: no — and it is inert, not merely unused

Check Result
What the substitution adds Exactly one condition, issue_number == pr_number. It does not widen the text searched. Brute force over 200k random (issue_number, pr_number, title) triples: 0 mismatches against ref(title, pr) or issue_number == pr
Can that condition occur? No — GitHub draws issue and PR numbers from one per-repo sequence. 202 dedupe-eligible candidates over recorded history (reverted/abandoned keepalive rows carrying a PR number, 2026-01-08..2026-08-22, 11 repos) vs 4,031 issues in those repos: 0 collisions
Would it matter if it did? No — every seed evidence_for_repo builds opens with PR #{pr_number}, so the adjacent _contains_pr_ref(candidate["seed"], issue_number) disjunct already fires in exactly that case. Differential over 120k real-shaped candidates with the collision forced in ~40% of search hits: 0 behaviour differences

Effect on matching: +0 candidates — not merely over history, but under any possible input. A dead
disjunct by construction. The only way it could ever fire is as a false positive claiming
duplication from a bare numeric coincidence.

This also corrects the premise behind the question: _contains_pr_ref already matches a bare
#N in an issue title — the # in #?{n} is optional and (?<!\d) excludes only a preceding
digit. So the case the combined form was supposedly for was already covered.

Nothing here changes what the path matches, so §2 is satisfied by the change being a true no-op
rather than by deferring it. The genuine widening, if ever wanted, is the sibling pattern in
durability_sweep.pytitle -> title+body, which widens the TEXT. That direction is recorded
in the comment.

Changes

  1. Dead binding removed; the reasoning recorded as a comment above the matching loop (not in
    the deleted line's slot, so an identical deletion on both sides merges cleanly rather than
    conflicting).

  2. Also took the other F841 in this file — an unused repo = target.split("#", 1)[0] in the
    selftest loop (record_run is passed target, never repo).

    Since superseded: main took both deletions itself when the lint debt was drained, so this
    PR's diff against main is now 46 insertions, 0 deletions — purely the decision record and
    the test gate, which is the part that could not be derived from a lint pass.

Test gate

Two selftest assertions pin the decision, with a deliberate-break -> revert demonstration:

  • wiring the combined haystack in -> assertion 1 fails (exit 1)
  • removing PR #<pr> from the reversal seed, which is the precondition that makes it a no-op ->
    assertion 2 fails (exit 1)
  • revert -> byte-for-byte identical, both pass (exit 0)
verify.py: 391 passed, 0 failed, 0/26 max skipped (floor 391)
           84 of 84 selftests ran, 0/7 max skipped; 5 of 5 gates green

CI note

lint-format, lint-ruff, typecheck-mypy, python 3.12 and python 3.13 were red on this
branch for a repo-wide reason unrelated to it: .github/workflows/autofix-versions.env was
missing, so all five died at the tooling-install step before running any code. That is now fixed
by #60, and all checks on this PR are green.

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in: 13 minutes

Limit details: You’ve used the included review currently available. Your 75 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bcab2ea4-da0a-425b-9063-0436391a0f58

📥 Commits

Reviewing files that changed from the base of the PR and between 6087d28 and 30be04f.

📒 Files selected for processing (1)
  • keepalive_evidence.py

Comment @coderabbitai help to get the list of available commands.

@agents-workflows-bot

agents-workflows-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Workflow source detected

PR #66 now has valid workflow source context (origin=local_request).

No linked GitHub issue is required for this PR.

@stranske-keepalive

stranske-keepalive Bot commented Aug 23, 2026

Copy link
Copy Markdown

Automated Status Summary

Head SHA: 13408e3
Latest Runs: ⏳ pending — Gate
Required: core tests (3.12): ⏳ pending, core tests (3.13): ⏳ pending, docker smoke: ⏳ pending, gate: ⏳ pending

Workflow / Job Result Logs
(no jobs reported) ⏳ pending

Updated automatically; will refresh on subsequent CI/Docker completions.


Keepalive checklist

Scope

No scope information available

Tasks

  • No tasks defined

Acceptance criteria

  • No acceptance criteria defined

@stranske

Copy link
Copy Markdown
Owner Author

Runner dispatch state for autofix on PR #66. Do not edit.

@stranske

Copy link
Copy Markdown
Owner Author

Runner dispatch state for codex on PR #66. Do not edit.

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Workflow state fingerprint for Agents Gate Followups. Do not edit.

@github-actions

Copy link
Copy Markdown
Contributor

Workflow state fingerprint for Keepalive Loop Reporter. Do not edit.

Tim Stranske and others added 2 commits August 23, 2026 09:21
…y it stays dead

`_dedupe_candidate` built `haystack = f"#{issue_number} {issue_title}"` and then searched
`issue_title` and `candidate["seed"]` only. ruff F841 flagged the unused local; PR #58 removed
the binding as lint. This decides the question that removal left open — was the combined form
the intent? No, and it is inert rather than merely unused:

  * Substituting `haystack` for `issue_title` in the first `_contains_pr_ref` adds EXACTLY one
    condition, `issue_number == pr_number`. It does not widen the text searched. Checked by brute
    force over 200k random (issue_number, pr_number, title) triples: 0 mismatches against
    `ref(title, pr) or issue_number == pr`.
  * That condition cannot occur: GitHub draws issue and PR numbers from one per-repo sequence.
    Over recorded history — 202 dedupe-eligible candidates (reverted/abandoned keepalive rows
    carrying a PR number, 2026-01-08..2026-08-22, 11 repos) against 4,031 issues in those same
    repos — 0 number collisions.
  * And it is already covered even if it could: every seed `evidence_for_repo` builds opens with
    "PR #{pr_number}", so the adjacent `_contains_pr_ref(candidate["seed"], issue_number)`
    disjunct is already true in exactly that case. Differential run of the real
    `_dedupe_candidate` against a haystack-wired copy over 120k real-shaped candidates, with
    `issue_number == pr_number` forced in ~40% of search hits: 0 behaviour differences.

So the effect on matching is +0 candidates, not merely over history but under any input. The one
way it could ever fire is as a false positive claiming duplication from a numeric coincidence.

Also corrects the premise behind the question: `_contains_pr_ref` ALREADY matches a bare "#N" in
an issue title — the "#" in `#?{n}` is optional and `(?<!\d)` excludes only a preceding digit.

Nothing here changes what the path matches, so CLAUDE.md §2 is satisfied by the change being a
true no-op rather than by deferring it. The genuine widening, if ever wanted, is the sibling
pattern in durability_sweep.py — title -> title+body, which widens the TEXT.

The explanation sits ABOVE the matching loop rather than in the deleted line's slot, so an
identical deletion on both sides merges cleanly with PR #58 instead of conflicting.

Selftest pins the decision with two assertions and a deliberate-break -> revert demonstration:
wiring the haystack in fails assertion 1 (exit 1); removing "PR #<pr>" from the reversal seed
fails assertion 2 (exit 1); revert restores byte-for-byte and both pass (exit 0).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…is redundant

PR #58's entire change to keepalive_evidence.py is two deletions: the dead `haystack` binding
(decided in the previous commit) and this unused `repo = target.split("#", 1)[0]` in the
selftest loop — `record_run` is passed `target`, never `repo`.

Taking both makes this branch's copy of the file a superset of #58's, and since both are now
identical deletions on both sides, the file merges with zero conflicts in either order —
verified with a 3-way merge against origin/claude/ci-conform-format-lint.

`ruff check --select E4,E7,E9,F` is clean for this file and `black --line-length 100 --check`
leaves it unchanged. #58 is separately CONFLICTING against main (it predates #59 and is a
tree-wide black pass), so it needs a rebase regardless — that is not caused by this branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@stranske
stranske force-pushed the claude/wizardly-bohr-4d6622 branch from 3566ffc to b29de06 Compare August 23, 2026 14:31
@stranske stranske changed the title claude/wizardly bohr 4d6622 fix(evidence): decide the dead combined-haystack binding — it stays removed, and why Aug 23, 2026
@stranske
stranske merged commit ddb0928 into main Aug 23, 2026
54 checks passed
@stranske
stranske deleted the claude/wizardly-bohr-4d6622 branch August 23, 2026 18:55
stranske pushed a commit that referenced this pull request Aug 23, 2026
main modified langsmith-fleet-worker-attempt.json again (pr_number 68, an
eleventh rewrite), producing the modify/delete conflict this change exists to
end. Resolved by keeping the deletion. Floor comes from main at 407; this
branch adds no tests, so the merge result is main's count unchanged.
stranske pushed a commit that referenced this pull request Aug 23, 2026
`langsmith-fleet-worker-attempt.json` was TRACKED at the repo root, holding one
run's execution telemetry (`operation_role: worker`, a resolved `selected_model`,
and the `pr_number` of whichever PR last merged). `stranske/Workflows`
`reusable-codex-run.yml` rewrites it into the checkout root on every agent round,
purely to stage the `actions/upload-artifact` step on the next line — so the
autofix bot re-emitted it on the next PR and #66 hit an add/add conflict on a
path neither side had a reason to author. That was dropped there; the cause was
not. It recurred during this branch's own review: #68's merge re-added the file,
turning the rebase into a modify/delete conflict, which is the defect
demonstrating itself.

Wrong in this repo's own terms, not merely noisy. CLAUDE.md §1's tool-vs-evidence
split puts one run's telemetry outside the tree. §2 requires execution
provenance to reach the learner through `feedback.py`'s tables, and a
git-churned `worker` record with a resolved model is a second, unmanaged store of
exactly that evidence. Nothing here reads the path — this tree's emitters are
`langsmith_*.py` and they read `langsmith-fleet.ndjson` from `$ORCH_STATE_DIR`.

Both steps are needed, and the order is not interchangeable. Verified against a
scratch repository: `git add -A` SKIPS an ignored UNTRACKED path but STAGES an
ignored TRACKED one. So ignoring alone would have been inert while the path
stayed in the index, and untracking alone would have let the next round re-add
it. It also means the runner's `git reset HEAD --` denylist is only load-bearing
for paths that are already tracked.

A PATTERN, not the literal name: the runner names the file after the ROLE it
recorded, so a verifier or evaluator attempt arrives as a sibling and escapes a
literal.

ROOT-ANCHORED, and the leading slash is load-bearing. The first draft bounded the
patterns by extension and treated that as sufficient; it is not. Unanchored, a
gitignore pattern matches at EVERY depth, so `langsmith-fleet*.json` also
swallowed a contract schema at
`docs/contracts/schemas/langsmith-fleet-v1.schema.json` — a path
`stranske/Workflows` really tracks, whose sibling
`artifact-manifest-v1.schema.json` this tree already keeps. A schema IS a
`.json`, so extension-bounding cannot save it. Found by a concurrent session
reviewing the upstream half of this fix. The nested CI artifact directory is
named separately as `artifacts/langsmith/` for the same reason: anchoring it to
the one directory that holds it cannot reach a schema elsewhere. Same near-miss
the vendored-`node_modules` exception records, same conclusion.

Placed ABOVE the managed `BEGIN/END WORKFLOWS STATUS FILES` block on purpose: a
Template-Version bump rewrites that block and would silently drop the line.
`scripts/sync_status_file_ignores.py --check` still reports all canonical
patterns present.

Guarded by `test_repo_artifact_hygiene.py` (11 tests), which puts every question
to git rather than reimplementing gitignore precedence — `.gitignore`'s own
header forbids the alternative, recording that trailing comments once made every
pattern inert and staged 795 files instead of 141. The new prerequisite is
`env_prereq.git_repo_absent()`, gated on whether git can answer here and never on
`.gitignore` itself, since gating on the measured file would leave the check
unable to fail once the file went missing.

Three break->revert directions are held, each demonstrated and reverted
byte-identical: too narrow (the bare literal fails the four sibling cases), too
broad by KIND (`*langsmith-fleet*` fails on the contract doc), too broad by DEPTH
(dropping the slash fails on the schema).

Floor 410 -> 421, re-measured on each merge result as main moved twice underneath
(402+10, then 407+10, then 410+11). No ceiling moved — the tests run on any
checkout and skip only in the exec-mirror layout, which is a file copy, not a
`.git`.

Upstream, handled separately: Workflows PR #2856 (2026-07-31) diagnosed this
verbatim but fixed only its own repo, never `templates/consumer-repo/.gitignore`,
so consumers stayed exposed and this repo repeated the defect 23 days later.
stranske pushed a commit that referenced this pull request Aug 23, 2026
`langsmith-fleet-worker-attempt.json` was TRACKED at the repo root, holding one
run's execution telemetry (`operation_role: worker`, a resolved `selected_model`,
and the `pr_number` of whichever PR last merged). `stranske/Workflows`
`reusable-codex-run.yml` rewrites it into the checkout root on every agent round,
purely to stage the `actions/upload-artifact` step on the next line — so the
autofix bot re-emitted it on the next PR and #66 hit an add/add conflict on a
path neither side had a reason to author. That was dropped there; the cause was
not. It recurred during this branch's own review: #68's merge re-added the file,
turning the rebase into a modify/delete conflict, which is the defect
demonstrating itself.

Wrong in this repo's own terms, not merely noisy. CLAUDE.md §1's tool-vs-evidence
split puts one run's telemetry outside the tree. §2 requires execution
provenance to reach the learner through `feedback.py`'s tables, and a
git-churned `worker` record with a resolved model is a second, unmanaged store of
exactly that evidence. Nothing here reads the path — this tree's emitters are
`langsmith_*.py` and they read `langsmith-fleet.ndjson` from `$ORCH_STATE_DIR`.

Both steps are needed, and the order is not interchangeable. Verified against a
scratch repository: `git add -A` SKIPS an ignored UNTRACKED path but STAGES an
ignored TRACKED one. So ignoring alone would have been inert while the path
stayed in the index, and untracking alone would have let the next round re-add
it. It also means the runner's `git reset HEAD --` denylist is only load-bearing
for paths that are already tracked.

A PATTERN, not the literal name: the runner names the file after the ROLE it
recorded, so a verifier or evaluator attempt arrives as a sibling and escapes a
literal.

ROOT-ANCHORED, and the leading slash is load-bearing. The first draft bounded the
patterns by extension and treated that as sufficient; it is not. Unanchored, a
gitignore pattern matches at EVERY depth, so `langsmith-fleet*.json` also
swallowed a contract schema at
`docs/contracts/schemas/langsmith-fleet-v1.schema.json` — a path
`stranske/Workflows` really tracks, whose sibling
`artifact-manifest-v1.schema.json` this tree already keeps. A schema IS a
`.json`, so extension-bounding cannot save it. Found by a concurrent session
reviewing the upstream half of this fix. The nested CI artifact directory is
named separately as `artifacts/langsmith/` for the same reason: anchoring it to
the one directory that holds it cannot reach a schema elsewhere. Same near-miss
the vendored-`node_modules` exception records, same conclusion.

Placed ABOVE the managed `BEGIN/END WORKFLOWS STATUS FILES` block on purpose: a
Template-Version bump rewrites that block and would silently drop the line.
`scripts/sync_status_file_ignores.py --check` still reports all canonical
patterns present.

Guarded by `test_repo_artifact_hygiene.py` (11 tests), which puts every question
to git rather than reimplementing gitignore precedence — `.gitignore`'s own
header forbids the alternative, recording that trailing comments once made every
pattern inert and staged 795 files instead of 141. The new prerequisite is
`env_prereq.git_repo_absent()`, gated on whether git can answer here and never on
`.gitignore` itself, since gating on the measured file would leave the check
unable to fail once the file went missing.

Three break->revert directions are held, each demonstrated and reverted
byte-identical: too narrow (the bare literal fails the four sibling cases), too
broad by KIND (`*langsmith-fleet*` fails on the contract doc), too broad by DEPTH
(dropping the slash fails on the schema).

Floor 410 -> 421, re-measured on each merge result as main moved twice underneath
(402+10, then 407+10, then 410+11). No ceiling moved — the tests run on any
checkout and skip only in the exec-mirror layout, which is a file copy, not a
`.git`.

Upstream, handled separately: Workflows PR #2856 (2026-07-31) diagnosed this
verbatim but fixed only its own repo, never `templates/consumer-repo/.gitignore`,
so consumers stayed exposed and this repo repeated the defect 23 days later.
stranske pushed a commit that referenced this pull request Aug 23, 2026
`langsmith-fleet-worker-attempt.json` was TRACKED at the repo root, holding one
run's execution telemetry (`operation_role: worker`, a resolved `selected_model`,
and the `pr_number` of whichever PR last merged). `stranske/Workflows`
`reusable-codex-run.yml` rewrites it into the checkout root on every agent round,
purely to stage the `actions/upload-artifact` step on the next line — so the
autofix bot re-emitted it on the next PR and #66 hit an add/add conflict on a
path neither side had a reason to author. That was dropped there; the cause was
not. It recurred during this branch's own review: #68's merge re-added the file,
turning the rebase into a modify/delete conflict, which is the defect
demonstrating itself.

Wrong in this repo's own terms, not merely noisy. CLAUDE.md §1's tool-vs-evidence
split puts one run's telemetry outside the tree. §2 requires execution
provenance to reach the learner through `feedback.py`'s tables, and a
git-churned `worker` record with a resolved model is a second, unmanaged store of
exactly that evidence. Nothing here reads the path — this tree's emitters are
`langsmith_*.py` and they read `langsmith-fleet.ndjson` from `$ORCH_STATE_DIR`.

Both steps are needed, and the order is not interchangeable. Verified against a
scratch repository: `git add -A` SKIPS an ignored UNTRACKED path but STAGES an
ignored TRACKED one. So ignoring alone would have been inert while the path
stayed in the index, and untracking alone would have let the next round re-add
it. It also means the runner's `git reset HEAD --` denylist is only load-bearing
for paths that are already tracked.

A PATTERN, not the literal name: the runner names the file after the ROLE it
recorded, so a verifier or evaluator attempt arrives as a sibling and escapes a
literal.

ROOT-ANCHORED, and the leading slash is load-bearing. The first draft bounded the
patterns by extension and treated that as sufficient; it is not. Unanchored, a
gitignore pattern matches at EVERY depth, so `langsmith-fleet*.json` also
swallowed a contract schema at
`docs/contracts/schemas/langsmith-fleet-v1.schema.json` — a path
`stranske/Workflows` really tracks, whose sibling
`artifact-manifest-v1.schema.json` this tree already keeps. A schema IS a
`.json`, so extension-bounding cannot save it. Found by a concurrent session
reviewing the upstream half of this fix. The nested CI artifact directory is
named separately as `artifacts/langsmith/` for the same reason: anchoring it to
the one directory that holds it cannot reach a schema elsewhere. Same near-miss
the vendored-`node_modules` exception records, same conclusion.

Placed ABOVE the managed `BEGIN/END WORKFLOWS STATUS FILES` block on purpose: a
Template-Version bump rewrites that block and would silently drop the line.
`scripts/sync_status_file_ignores.py --check` still reports all canonical
patterns present.

Guarded by `test_repo_artifact_hygiene.py` (11 tests), which puts every question
to git rather than reimplementing gitignore precedence — `.gitignore`'s own
header forbids the alternative, recording that trailing comments once made every
pattern inert and staged 795 files instead of 141. The new prerequisite is
`env_prereq.git_repo_absent()`, gated on whether git can answer here and never on
`.gitignore` itself, since gating on the measured file would leave the check
unable to fail once the file went missing.

Three break->revert directions are held, each demonstrated and reverted
byte-identical: too narrow (the bare literal fails the four sibling cases), too
broad by KIND (`*langsmith-fleet*` fails on the contract doc), too broad by DEPTH
(dropping the slash fails on the schema).

Floor 410 -> 421, re-measured on each merge result as main moved twice underneath
(402+10, then 407+10, then 410+11). No ceiling moved — the tests run on any
checkout and skip only in the exec-mirror layout, which is a file copy, not a
`.git`.

Upstream, handled separately: Workflows PR #2856 (2026-07-31) diagnosed this
verbatim but fixed only its own repo, never `templates/consumer-repo/.gitignore`,
so consumers stayed exposed and this repo repeated the defect 23 days later.
stranske pushed a commit that referenced this pull request Aug 23, 2026
`langsmith-fleet-worker-attempt.json` was TRACKED at the repo root, holding one
run's execution telemetry (`operation_role: worker`, a resolved `selected_model`,
and the `pr_number` of whichever PR last merged). `stranske/Workflows`
`reusable-codex-run.yml` rewrites it into the checkout root on every agent round,
purely to stage the `actions/upload-artifact` step on the next line — so the
autofix bot re-emitted it on the next PR and #66 hit an add/add conflict on a
path neither side had a reason to author. That was dropped there; the cause was
not. It recurred during this branch's own review: #68's merge re-added the file,
turning the rebase into a modify/delete conflict, which is the defect
demonstrating itself.

Wrong in this repo's own terms, not merely noisy. CLAUDE.md §1's tool-vs-evidence
split puts one run's telemetry outside the tree. §2 requires execution
provenance to reach the learner through `feedback.py`'s tables, and a
git-churned `worker` record with a resolved model is a second, unmanaged store of
exactly that evidence. Nothing here reads the path — this tree's emitters are
`langsmith_*.py` and they read `langsmith-fleet.ndjson` from `$ORCH_STATE_DIR`.

Both steps are needed, and the order is not interchangeable. Verified against a
scratch repository: `git add -A` SKIPS an ignored UNTRACKED path but STAGES an
ignored TRACKED one. So ignoring alone would have been inert while the path
stayed in the index, and untracking alone would have let the next round re-add
it. It also means the runner's `git reset HEAD --` denylist is only load-bearing
for paths that are already tracked.

A PATTERN, not the literal name: the runner names the file after the ROLE it
recorded, so a verifier or evaluator attempt arrives as a sibling and escapes a
literal.

ROOT-ANCHORED, and the leading slash is load-bearing. The first draft bounded the
patterns by extension and treated that as sufficient; it is not. Unanchored, a
gitignore pattern matches at EVERY depth, so `langsmith-fleet*.json` also
swallowed a contract schema at
`docs/contracts/schemas/langsmith-fleet-v1.schema.json` — a path
`stranske/Workflows` really tracks, whose sibling
`artifact-manifest-v1.schema.json` this tree already keeps. A schema IS a
`.json`, so extension-bounding cannot save it. Found by a concurrent session
reviewing the upstream half of this fix. The nested CI artifact directory is
named separately as `artifacts/langsmith/` for the same reason: anchoring it to
the one directory that holds it cannot reach a schema elsewhere. Same near-miss
the vendored-`node_modules` exception records, same conclusion.

Placed ABOVE the managed `BEGIN/END WORKFLOWS STATUS FILES` block on purpose: a
Template-Version bump rewrites that block and would silently drop the line.
`scripts/sync_status_file_ignores.py --check` still reports all canonical
patterns present.

Guarded by `test_repo_artifact_hygiene.py` (11 tests), which puts every question
to git rather than reimplementing gitignore precedence — `.gitignore`'s own
header forbids the alternative, recording that trailing comments once made every
pattern inert and staged 795 files instead of 141. The new prerequisite is
`env_prereq.git_repo_absent()`, gated on whether git can answer here and never on
`.gitignore` itself, since gating on the measured file would leave the check
unable to fail once the file went missing.

Three break->revert directions are held, each demonstrated and reverted
byte-identical: too narrow (the bare literal fails the four sibling cases), too
broad by KIND (`*langsmith-fleet*` fails on the contract doc), too broad by DEPTH
(dropping the slash fails on the schema).

Floor 410 -> 421, re-measured on each merge result as main moved twice underneath
(402+10, then 407+10, then 410+11). No ceiling moved — the tests run on any
checkout and skip only in the exec-mirror layout, which is a file copy, not a
`.git`.

Upstream, handled separately: Workflows PR #2856 (2026-07-31) diagnosed this
verbatim but fixed only its own repo, never `templates/consumer-repo/.gitignore`,
so consumers stayed exposed and this repo repeated the defect 23 days later.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant