Resolve labels from the org, and never touch a held issue #4

Merged
claude merged 1 commits from fix/org-labels-and-hands-off into main 2026-09-07 16:10:21 +00:00
Member

Closes #3. Two fixes — the first is a live regression I caused today, the
second stops one before it arms.

1. The reconciler was a silent no-op

label_id() resolved the label from repos/{repo}/labels only. Labels moved
to the organisation today (weblib-archive#63), that endpoint now returns []
in all five repos, so it returned None everywhere:

$ sync_blocked_label.py --repo weblib/weblib-archive --dry-run
  skipped: no 'Status/Blocked' label in this repo
--> 0 change(s)

It failed safe — skipping rather than mislabelling, which is exactly what
that docstring was written for — but a job running every 15 minutes was
reporting success while doing nothing at all. Now it tries the repo, then the
org, so it does not care how a given instance is arranged.

2. HANDS_OFF — never touch a held issue

the reconciler must not touch issues that are already on hold or abandoned
— bit

Implemented literally: neither add nor remove, and the issue is skipped
before its dependencies are even fetched.

This is not cosmetic. Status/* is becoming exclusive again, and under that an
add does not sit beside an existing status — it replaces it. So the
reconciler would silently delete a deliberate Status/On Hold on its next
pass. And On Hold is precisely what makes the backlog sweep skip an issue, so
a parked issue would quietly become an available one, with nothing in the log
to explain it.

Status/Need More Info is deliberately not in HANDS_OFF, because that is
what bit specified. Worth knowing that under exclusive Status/* it can still
be evicted by an automatic Blocked — flagged rather than silently widened,
since it is bit's rule to set.

Verified against the live forge, not by reading

Add path, org-resolved — stripped Status/Blocked off cfbypass#8, which is
genuinely blocked by #9 and #7:

would add    Status/Blocked on weblib/cfbypass#8  (blocked by #9, #7)   [dry-run]
add          Status/Blocked on weblib/cfbypass#8  (blocked by #9, #7)   [real]
after: Kind/Feature, Priority/Medium, Status/Blocked, Type/Task

Hands-off, as a control — one issue, one open blocker, changing only the
label:

without Status/On Hold ->  "would add Status/Blocked ... (blocked by #54)"   1 change
with    Status/On Hold ->  "hands off weblib/weblib-archive#62"              0 changes

Same issue, same dependency, opposite outcome — so the guard is doing the work
and not merely coinciding with a no-op run. The temporary dependency was
removed afterwards and #62's labels are intact.

Note on the log line

A skipped issue now prints hands off <repo>#<n> (Status/On Hold). The failure
this replaces was invisible; anything the reconciler declines to do should say
so, or the next person debugging it is back where I was ten minutes ago.

Closes #3. Two fixes — the first is a live regression I caused today, the second stops one before it arms. ## 1. The reconciler was a silent no-op `label_id()` resolved the label from `repos/{repo}/labels` only. Labels moved to the organisation today (weblib-archive#63), that endpoint now returns `[]` in all five repos, so it returned `None` everywhere: ``` $ sync_blocked_label.py --repo weblib/weblib-archive --dry-run skipped: no 'Status/Blocked' label in this repo --> 0 change(s) ``` It failed **safe** — skipping rather than mislabelling, which is exactly what that docstring was written for — but a job running every 15 minutes was reporting success while doing nothing at all. Now it tries the repo, then the org, so it does not care how a given instance is arranged. ## 2. `HANDS_OFF` — never touch a held issue > the reconciler must not touch issues that are already on hold or abandoned > — bit Implemented literally: **neither add nor remove**, and the issue is skipped before its dependencies are even fetched. This is not cosmetic. `Status/*` is becoming exclusive again, and under that an add does not sit *beside* an existing status — it **replaces** it. So the reconciler would silently delete a deliberate `Status/On Hold` on its next pass. And `On Hold` is precisely what makes the backlog sweep skip an issue, so a parked issue would quietly become an available one, with nothing in the log to explain it. `Status/Need More Info` is deliberately **not** in `HANDS_OFF`, because that is what bit specified. Worth knowing that under exclusive `Status/*` it can still be evicted by an automatic `Blocked` — flagged rather than silently widened, since it is bit's rule to set. ## Verified against the live forge, not by reading **Add path, org-resolved** — stripped `Status/Blocked` off cfbypass#8, which is genuinely blocked by #9 and #7: ``` would add Status/Blocked on weblib/cfbypass#8 (blocked by #9, #7) [dry-run] add Status/Blocked on weblib/cfbypass#8 (blocked by #9, #7) [real] after: Kind/Feature, Priority/Medium, Status/Blocked, Type/Task ``` **Hands-off, as a control** — one issue, one open blocker, changing only the label: ``` without Status/On Hold -> "would add Status/Blocked ... (blocked by #54)" 1 change with Status/On Hold -> "hands off weblib/weblib-archive#62" 0 changes ``` Same issue, same dependency, opposite outcome — so the guard is doing the work and not merely coinciding with a no-op run. The temporary dependency was removed afterwards and #62's labels are intact. ## Note on the log line A skipped issue now prints `hands off <repo>#<n> (Status/On Hold)`. The failure this replaces was invisible; anything the reconciler declines to do should say so, or the next person debugging it is back where I was ten minutes ago.
claude added 1 commit 2026-09-07 16:07:46 +00:00
Two fixes. The first is a live regression I caused today; the second stops one
before it arms.

1. `label_id()` looked the label up in `repos/{repo}/labels` only. Labels moved
   to the organisation today (weblib-archive#63) and that endpoint now returns
   `[]` in all five repos, so it returned None everywhere and the whole script
   became a silent no-op:

       $ sync_blocked_label.py --repo weblib/weblib-archive --dry-run
         skipped: no 'Status/Blocked' label in this repo
       --> 0 change(s)

   It failed *safe* - skipping rather than mislabelling, which is what that
   docstring was written for - but a job that runs every 15 minutes reported
   success while doing nothing. It now tries the repo, then the org, so it does
   not care how an instance is arranged.

2. bit, 2026-09-07: "the reconciler must not touch issues that are already on
   hold or abandoned". Implemented literally - neither add nor remove.

   This matters because `Status/*` is becoming exclusive again. Under that,
   adding `Status/Blocked` does not sit beside an existing status, it
   *replaces* it - so the reconciler would silently delete a deliberate
   `Status/On Hold` on its next pass. And since On Hold is exactly what makes
   the backlog sweep skip an issue, a parked issue would quietly become an
   available one, with nothing in the log to say why.

Verified against the live forge rather than by reading:

  * add path, org-resolved: stripped Status/Blocked off cfbypass#8, dry-run
    said "would add", the real run added it back
  * hands-off, as a control on ONE issue with ONE open blocker, changing only
    the label:
        without Status/On Hold ->  "would add Status/Blocked ... (blocked by #54)"
        with    Status/On Hold ->  "hands off", 0 changes, label intact

Closes #3

Co-authored-by: bit <bit@das-labor.org>
claude added the
Priority
High
2
Type
Bug
labels 2026-09-07 16:07:46 +00:00
claude requested review from bit 2026-09-07 16:09:00 +00:00
Author
Member

Needs your approval — I cannot merge this one.

POST /repos/weblib/weblib-ci/pulls/4/merge
{"message":"Does not have enough approvals"}

main is protected here and weblib-ci has no development branch, so every PR
in this repo targets main and needs you. Review requested.

Until it merges the reconciler does nothing, in all five repos, and says so
only in its own log:

weblib/weblib-archive:
  skipped: no 'Status/Blocked' label in this repo
--> 0 change(s)

The 15-minute job keeps reporting success. Nothing is being corrupted — it
fails safe — but Status/Blocked has not tracked the dependency graph since
the label migration earlier today, and will not until this lands.

Both fixes are verified against the live forge, and the second one is the rule
you gave me:

the reconciler must not touch issues that are already on hold or abandoned

Implemented literally — neither add nor remove, skipped before its dependencies
are even fetched. The control test is on the PR body: same issue, same open
blocker, only the label differing — would add without Status/On Hold,
hands off with it.

**Needs your approval — I cannot merge this one.** ``` POST /repos/weblib/weblib-ci/pulls/4/merge {"message":"Does not have enough approvals"} ``` `main` is protected here and weblib-ci has no `development` branch, so every PR in this repo targets `main` and needs you. Review requested. **Until it merges the reconciler does nothing**, in all five repos, and says so only in its own log: ``` weblib/weblib-archive: skipped: no 'Status/Blocked' label in this repo --> 0 change(s) ``` The 15-minute job keeps reporting success. Nothing is being corrupted — it fails safe — but `Status/Blocked` has not tracked the dependency graph since the label migration earlier today, and will not until this lands. Both fixes are verified against the live forge, and the second one is the rule you gave me: > the reconciler must not touch issues that are already on hold or abandoned Implemented literally — neither add nor remove, skipped before its dependencies are even fetched. The control test is on the PR body: same issue, same open blocker, only the label differing — `would add` without `Status/On Hold`, `hands off` with it.
bit approved these changes 2026-09-07 16:09:31 +00:00
claude merged commit 259bdd93ca into main 2026-09-07 16:10:21 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Reference: weblib/weblib-ci#4