Skip to content

Document the no-issue PR exception - #47

Merged
gavinbee merged 2 commits into
mainfrom
46-document-no-issue-exception
Sep 4, 2026
Merged

Document the no-issue PR exception#47
gavinbee merged 2 commits into
mainfrom
46-document-no-issue-exception

Conversation

@gavinbee

@gavinbee gavinbee commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Refs #46deliberately does not close it. This lands the documentation half; label reconciliation in apply-settings.py is the remaining work on that issue.

What & why

AGENTS.md §3 said "For every change: 1. Open a GitHub issue first", and CONTRIBUTING.md said the same. We stopped meaning it — a change small enough to explain itself now goes straight to a PR labelled no-issue, with a NO-ISSUE: <reason> trailer. First used in deck-eval-gen#20.

Undocumented, that leaves the rule and the practice disagreeing, so the next agent either files an issue for a typo or reads a no-issue PR as a violation of the canonical guide.

Three touch points, not one

The exception affects more than step 1 — a no-issue PR also has no number for the branch name, and nothing to put in Closes #N:

Step Change
1 the exception itself, with what qualifies
2 branch may use a descriptive slug when there is no number
5 (CONTRIBUTING) the NO-ISSUE: trailer stands in for Closes #N

AGENTS.md gets the short form; CONTRIBUTING.md gets a ### The no-issue exception section carrying the reasoning, since that file is where the why lives.

The label

settings.yml gains a labels: block naming no-issue — the canonical set, not yet applied. apply-settings.py acts on repository, branches and rulesets only, so labels are still created per repo with gh label create (as deck-eval-gen needed).

Teaching the reconciler to apply the block is in scope for #46 and is why this PR leaves it open. An earlier revision of the comment pointed at #46 as the thing that would wire it up while this PR closed it — a dangling reference that would have cited a closed issue, exactly the bare-TODO-with-a-link that AGENTS.md §4 forbids. Fixed in 801effe.

Verified inert: the YAML parses, repository and rulesets are unaffected, and the script loads unchanged.

Note on this PR

The new text says the exception does not cover changes to the rules themselves — a policy whose reasoning nobody can find gets re-litigated. So this change has an issue, deliberately.

The first commit's message still reads Closes #46. Harmless: settings.yml sets squash_merge_commit_message: PR_BODY, so the squash commit takes this description, not the branch commits' messages.

Testing

Docs plus one inert YAML block. ruff check . clean, pytest -q 11 passed — both unchanged, before and after the fixup.

Checklist

  • Linked the issue this PR relates to
  • Tests added/updated and passing — n/a, no behaviour changed
  • ruff check . clean (Python repos)
  • Docs/README updated if user-facing behaviour changed

🤖 Generated with Claude Code

gavinbee and others added 2 commits September 3, 2026 22:51
AGENTS.md said "For every change: open a GitHub issue first", and
CONTRIBUTING.md said the same. We stopped meaning it — a one-bullet docs
fix now goes straight to a PR labelled no-issue, with a NO-ISSUE trailer
saying why. Leaving that undocumented means the next agent either files
an issue for a typo or reads a no-issue PR as a rule violation.

The exception touches three steps, not one: a no-issue PR also has no
number for the branch name, and nothing to put in "Closes #N". All three
are amended in both files, with the reasoning in CONTRIBUTING.md, which
is where the why belongs.

settings.yml gains a labels block naming no-issue, recorded but not
applied — apply-settings.py reads only repository, branches and rulesets,
so labels are still created per repo by hand. Documenting a key we don't
act on yet is this file's stated convention; wiring it up is left to the
issue.

The exception explicitly does not cover changes to the rules themselves,
which is why this one has an issue.

Closes #46

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgLE8cg9huy2EZnRoXhmwp
The comment said wiring up label reconciliation was tracked by #46 — the
issue this PR was closing. That made it a dangling reference: on merge it
would have cited a closed issue that never did the wiring, which is the
bare-TODO-with-a-link that AGENTS.md section 4 exists to prevent.

Label reconciliation is in scope for #46, so the fix is for this PR to
stop closing it. The comment now says what remains and that #46 stays
open until it lands.

Refs #46

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgLE8cg9huy2EZnRoXhmwp
@gavinbee
gavinbee merged commit a6ee730 into main Sep 4, 2026
1 check passed
@gavinbee
gavinbee deleted the 46-document-no-issue-exception branch September 4, 2026 03:05
@gavinbee gavinbee mentioned this pull request Sep 4, 2026
4 tasks
gavinbee added a commit that referenced this pull request Sep 4, 2026
Closes #46

## What & why

[#47](#47) added the `labels:`
block to `settings.yml` as a documented-but-inert key: it named the
canonical set, but `apply-settings.py` read only `repository`,
`branches` and `rulesets`, so `no-issue` still had to be created by hand
in every repo. This wires it up — the rest of #46.

`apply-settings.py` now creates a label that is missing and corrects one
whose colour or description drifted. Labels do not depend on visibility,
so private repos get them too.

## Two decisions worth reviewing

**Reconciliation is additive.** A label named in `settings.yml` is
created or corrected; labels the YAML does not mention are left alone.
Repos carry GitHub's defaults plus the ones Dependabot creates
(`dependencies`, `python`), and deleting a label strips it from every
issue and PR that used it — not something a weekly scheduled job should
do unprompted. `settings.yml` is the minimum set, not the whole set.
Making it authoritative and pruning is a different and much more
destructive script; if that is wanted, it should be its own decision.

**Verification re-reads and compares.** `verify_labels` lists the labels
again and diffs them against the YAML, rather than assuming the write
took. That is deliberately *not* the shape of `verify_ruleset`, which
only checks that a ruleset name exists — the subject of #45. Adding a
second instance of that blind spot while #45 is open seemed like the
wrong move.

## Normalisation

Three ways a naive comparison would report false drift, all covered by
tests:

- GitHub stores colours as six lowercase hex digits with no `#`, so
`#EDEDED` in YAML and `ededed` from the API are the same colour.
- The API returns `null`, not `""`, for a label with no description;
that compares equal to an absent description in YAML.
- A YAML entry with no `color` does not blank the colour already on the
repo.

Label names are matched case-insensitively, since GitHub treats them
that way — a `No-Issue` on the repo will not read as missing against a
`no-issue` here.

`list_labels` pages with `--paginate --jq '.[]'`, which emits one
compact object per line; `--paginate` alone concatenates raw JSON arrays
into something `json.loads` cannot read.

## Testing

`ruff check .` clean. `pytest -q` — **22 passed**, up from 11; the 11
new tests cover `normalize_color` and `label_updates`, both pure.

Dry-run against real repos, read-only, no writes:

```
swimblocks/deck-eval-gen: 12 labels; has no-issue = True
  -> 'no-issue' present; in sync
swimblocks/.github: 9 labels; has no-issue = False
  -> would CREATE 'no-issue'
```

The first case is the useful one: `deck-eval-gen`'s `no-issue` was
created by hand with `gh label create`, and the reconciler reads it as
already in sync — so the normalisation and comparison agree with what
GitHub actually stores.

Once this merges, the next reconciler run creates `no-issue` everywhere
and the per-repo `gh label create` step goes away.

## Checklist

- [x] Linked the issue this PR closes
- [x] Tests added/updated and passing
- [x] `ruff check .` clean (Python repos)
- [x] Docs/README updated if user-facing behaviour changed —
`settings.yml`'s comment now says the block is applied, and describes
the additive rule

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.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