Pool demand queries exclude hold:* labels only: a bead held by metadata.hold_reason reads as claimable work to every pool — measured 6 held, 2 served up by gc hook #25

Open
opened 2026-09-09 19:36:15 +00:00 by forgejo-admin · 2 comments

Measured 2026-09-09 19:32Z by polecat gastown.capable in the celilo rig.

The mayor's hold mechanism in actual use is metadata.hold_reason (free text, e.g. "HELD 2026-09-04T07:20Z by gastown.mayor ... UNHOLD WHEN ce-ashq reports a green census"). The pool demand query excludes hold:mayor and hold:external labels only. It never reads metadata.hold_reason. So a bead held by metadata reads as claimable demand to every pool, while the hold text itself says do not work it.

Evidence

The label-filtered view (the exact query the pool demand uses):

gc bd ready --metadata-field 'gc.routed_to=celilo/gastown.polecat' --unassigned \
  --exclude-label 'hold:mayor' --exclude-label 'hold:external' --json

returns 2 beads: ce-yugi, ce-cfzx.

The same query without the label exclusions, cross-checked per bead against gc bd show, returns 6 pool-routed beads, and all 6 carry metadata.hold_reason:

bead hold source
ce-yugi mayor, 2026-09-04
ce-cfzx mayor, 2026-09-04
ce-26s1 e2e-lock, 2026-09-05
ce-ko64 mayor, 2026-09-07
ce-kvc6 mayor, 2026-09-07
ce-hct1 mayor, 2026-09-07

gc hook for the celilo polecat pool returned ce-yugi and ce-cfzx as work. Both carry an explicit unhold condition that is not met (the census bead ce-ashq closed 2026-09-06 at 41 of 54 measured, not green, and e2e-lock.sh status still reads HELD). A polecat that trusts the hook claims a bead the mayor deliberately fenced off.

This is the documented empty/narrow-answer shape (gc-scratch AGENTS.md, the entry behind ce-2qm): the instrument answers a narrower question than asked and says nothing about the difference. Here the answer is not empty but too wide, which is worse, because it invites a claim.

celilo/gascity#23 covers the hold-label check fighting cache-reconcile over gc.routed_to. That is about the hold:<role> label convention. This defect is that the real hold convention in use (metadata) is invisible to the demand filter. Different mechanism, same family of risk: a held bead looks claimable.

Fix options

  1. Add an exclude on hold_reason metadata (a negated metadata-field filter on bd ready and the hook dispatch query) and use it everywhere pool demand is probed. Simplest, matches the convention the mayor already uses.
  2. Standardize on hold labels and have the mayor write hold:mayor alongside hold_reason. Keeps one filter but asks the mayor to maintain two fields per hold, and every existing hold has to migrate.

Option 1 is the smaller change and matches how holds are actually written today.

The pinned pack has the same defect

The gastown pack's probe_pool_demand (the shell fallback polecats run from their startup prompt) filters the same two labels and not the metadata. A fix in the pack reaches this city only on a deliberate re-pin of 3b3b89f2011e06d84459aa7bea1552382f13930a; the binary fix reaches on a rebuild of /Users/pbanka/go/bin/gc. The pack change should be its own follow-up; I did not file a separate pack issue.

Workaround used

I read metadata.hold_reason on every bead the hook returned before claiming. All six pool beads are held, so I claimed nothing. Every future polecat has to know to do this by hand until the filter lands.

**Measured 2026-09-09 19:32Z by polecat gastown.capable in the celilo rig.** The mayor's hold mechanism in actual use is `metadata.hold_reason` (free text, e.g. "HELD 2026-09-04T07:20Z by gastown.mayor ... UNHOLD WHEN ce-ashq reports a green census"). The pool demand query excludes `hold:mayor` and `hold:external` **labels** only. It never reads `metadata.hold_reason`. So a bead held by metadata reads as claimable demand to every pool, while the hold text itself says do not work it. ## Evidence The label-filtered view (the exact query the pool demand uses): ``` gc bd ready --metadata-field 'gc.routed_to=celilo/gastown.polecat' --unassigned \ --exclude-label 'hold:mayor' --exclude-label 'hold:external' --json ``` returns 2 beads: `ce-yugi`, `ce-cfzx`. The same query without the label exclusions, cross-checked per bead against `gc bd show`, returns 6 pool-routed beads, and **all 6 carry `metadata.hold_reason`**: | bead | hold source | |---|---| | ce-yugi | mayor, 2026-09-04 | | ce-cfzx | mayor, 2026-09-04 | | ce-26s1 | e2e-lock, 2026-09-05 | | ce-ko64 | mayor, 2026-09-07 | | ce-kvc6 | mayor, 2026-09-07 | | ce-hct1 | mayor, 2026-09-07 | `gc hook` for the celilo polecat pool returned ce-yugi and ce-cfzx as work. Both carry an explicit unhold condition that is not met (the census bead ce-ashq closed 2026-09-06 at 41 of 54 measured, not green, and `e2e-lock.sh status` still reads HELD). A polecat that trusts the hook claims a bead the mayor deliberately fenced off. This is the documented empty/narrow-answer shape (gc-scratch AGENTS.md, the entry behind ce-2qm): the instrument answers a narrower question than asked and says nothing about the difference. Here the answer is not empty but too wide, which is worse, because it invites a claim. ## Related but distinct celilo/gascity#23 covers the hold-label check fighting cache-reconcile over `gc.routed_to`. That is about the `hold:<role>` label convention. This defect is that the real hold convention in use (metadata) is invisible to the demand filter. Different mechanism, same family of risk: a held bead looks claimable. ## Fix options 1. Add an exclude on `hold_reason` metadata (a negated metadata-field filter on `bd ready` and the hook dispatch query) and use it everywhere pool demand is probed. Simplest, matches the convention the mayor already uses. 2. Standardize on hold labels and have the mayor write `hold:mayor` alongside `hold_reason`. Keeps one filter but asks the mayor to maintain two fields per hold, and every existing hold has to migrate. Option 1 is the smaller change and matches how holds are actually written today. ## The pinned pack has the same defect The gastown pack's `probe_pool_demand` (the shell fallback polecats run from their startup prompt) filters the same two labels and not the metadata. A fix in the pack reaches this city only on a deliberate re-pin of `3b3b89f2011e06d84459aa7bea1552382f13930a`; the binary fix reaches on a rebuild of `/Users/pbanka/go/bin/gc`. The pack change should be its own follow-up; I did not file a separate pack issue. ## Workaround used I read `metadata.hold_reason` on every bead the hook returned before claiming. All six pool beads are held, so I claimed nothing. Every future polecat has to know to do this by hand until the filter lands.
Author
Owner

Reproduction context from the mayor (relayed by gastown.capable, 2026-09-09): the gap was reproduced by lifting five beads (ce-yugi, ce-cfzx, ce-bfut, ce-bhui, ce-b9gk) label-only. Removing the hold:mayor LABEL made them claimable to the supervisor's pool query while the hold_reason METADATA still said held. A polecat that read the bead rather than the label refused to claim, which is the disagreement this issue describes.

Additional instance measured the same day: two of the five lifted beads are chained slices whose predecessors are still held (ce-bfut is blocked by ce-1vb under the blanket pause; ce-b9gk is blocked by ce-bfut). gc bd ready correctly excluded both while the lifted hold_reason text invited claiming. A reader filtering on metadata instead of labels would serve work that cannot be done.

Reproduction context from the mayor (relayed by gastown.capable, 2026-09-09): the gap was reproduced by lifting five beads (ce-yugi, ce-cfzx, ce-bfut, ce-bhui, ce-b9gk) label-only. Removing the hold:mayor LABEL made them claimable to the supervisor's pool query while the hold_reason METADATA still said held. A polecat that read the bead rather than the label refused to claim, which is the disagreement this issue describes. Additional instance measured the same day: two of the five lifted beads are chained slices whose predecessors are still held (ce-bfut is blocked by ce-1vb under the blanket pause; ce-b9gk is blocked by ce-bfut). `gc bd ready` correctly excluded both while the lifted hold_reason text invited claiming. A reader filtering on metadata instead of labels would serve work that cannot be done.
Author
Owner

Reproduction from the capable polecat worktree, 2026-09-10: gastown.mayor reproduced this exactly while lifting five held beads on peba's instruction. The mayor removed the hold:mayor LABEL only and left the hold_reason METADATA saying the beads were held pending conditions (one still cited a stale census figure). The pool query served the beads on labels; the bead text said held; a worker read the bead instead of the label, refused to claim, and the gap was confirmed. The mayor's own write-up of the incident is on the thread with gs-wisp-60tvp (mayor → capable, 2026-09-09 19:38Z).

Reproduction from the capable polecat worktree, 2026-09-10: gastown.mayor reproduced this exactly while lifting five held beads on peba's instruction. The mayor removed the `hold:mayor` LABEL only and left the `hold_reason` METADATA saying the beads were held pending conditions (one still cited a stale census figure). The pool query served the beads on labels; the bead text said held; a worker read the bead instead of the label, refused to claim, and the gap was confirmed. The mayor's own write-up of the incident is on the thread with gs-wisp-60tvp (mayor → capable, 2026-09-09 19:38Z).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
celilo/gascity#25
No description provided.