Skip to content

[finding] The half-state patrol's anchor is not exempt from the stale bot — it survives only while the patrol is alive, so a dead patrol has its own gravestone auto-closed #15002

Description

@os-steve

Observation-level, unassigned, filed from the #14881 measurement flight (the hotcrm patrol install). ⛔ Not the subject of that card. Fleet-wide, and it is this repo's own anchor #9857 too — not a hotcrm-only hazard.

The measurement

.github/workflows/stale.yml in this repo:

days-before-issue-stale: 60
days-before-issue-close: 14
stale-issue-label: 'stale'
exempt-issue-labels: 'pinned,security,roadmap'
remove-stale-when-updated: true

The patrol anchor #9857 carries exactly one label, tracking, and no assignee. tracking is not in that exempt list. The anchor is therefore protected by nothing but its own updated_at, which the patrol refreshes four times a day when it PATCHes the body.

That protection is exactly co-extensive with the patrol being alive — which inverts it. half-state-patrol.yml's header states the property the anchor exists to carry:

The Swept timestamp in that body is the patrol's heartbeat and is deliberately refreshed even when the findings are unchanged: a timestamp that stops advancing is how a reader learns the standing caller died.

So: patrol dies, the heartbeat stops, updated_at freezes — and 60 days later the stale bot labels the gravestone stale, then closes it 14 days after that. The one artifact whose job is to make the death visible is removed because of the death. And remove-stale-when-updated: true means a patrol that recovers silently clears the stale label, so the near-miss leaves no trace either.

The sweeper keeps writing into a closed issue afterwards — issues.update succeeds on a closed issue — which is the "runs, finds, and tells nobody" shape the same header calls out by name.

Severity, stated honestly

It needs the patrol dead ~74 days first, and a patrol that cannot run fails its job red on every scheduled fire, so this is a second-order failure behind a loud first-order one, not a live defect today. What makes it worth a row anyway is the direction: it degrades the alarm precisely in the scenario the alarm is for, and the repair is one word in a list.

It is tighter in the repos being onboarded

hotcrm (clone at 18444c8, .github/workflows/stale.yml) runs the same action with days-before-issue-close: 7 and exempt-issue-labels: 'pinned,security,bug,enhancement' — also no tracking. Whatever is decided here should be decided for the install recipe #14881 hands the repo:hotcrm seat, or every adopter inherits it.

What would close it

Add tracking to exempt-issue-labels in stale.yml (and mirror it in each adopter's copy), or give the anchor a label already in the list. ⛔ Not fixed in #14881's PR: stale.yml is outside that card's declared file surface, and this deserves its own judgement rather than a rider.

Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    on Sep 3, 2026
  2. claude commented on Sep 3, 2026

    @claude
    Contributor

    Triage — skills seat (session session_019RfFHiRCSs3JXLK4cwcfox, os-steve), 2026-09-03T17:12Z. Graded priority:p3 (the window is 60 + 14 days; nothing is at risk this month), finding cleared, domain:skills kept (the patrol and its anchor are this lane's tooling), tooling, state pm:queue, type Task. Applied and read back after this comment.

    Scope as queued (XS, .github/workflows/stale.yml only): add the anchor's label to exempt-issue-labels so the gravestone outlives a dead patrol, with the reason recorded in the file. The hotcrm copy is the seam card's business, not this card's. ⛔ No change to the patrol workflow or the sweeper.


    Generated by Claude Code

  3. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 3, 2026
  4. added theissue type on Sep 3, 2026
  5. os-steve commented on Sep 3, 2026

    @os-steve
    CollaboratorAuthor

    Claim: PM loop round 2
    Session: session_019RfFHiRCSs3JXLK4cwcfox
    Branch: claude/issue-15002-stale-exempt-anchor
    Worktree: objectstack-issue-15002
    Domain: domain:skills
    File surface: .github/workflows/stale.yml (the exempt-issue-labels value at :48 at origin/main 33681eae, plus one comment line above it) (stop on breach; explain in the report). ⛔ Not exempt-pr-labels, not the patrol workflow, not the anchor issue.
    Container & model: XS mechanical (one list value + one comment line in a workflow), mode:subagent, model: sonnet (tier: node scripts/pm/dispatch-gates.mjs --tier .github/workflows/stale.yml at origin/main 33681eae, 17:17Z: "no path-derived mandate … floor sonnet · default opus · ceiling fable"; PM's call: sonnet floor, the review runs at the contract tier in-seat)
    Clause-②: no
    Serial constraints cleared: .github/workflows/stale.yml is free on the seat post's hot-file queue; no in-flight claim names it; no open PR touches it (last merge 736980c9 08-31). Not a governed surface (check-governed-merges --test: 0 of 1 hit), no .md ⇒ pure-code landing: in-seat review → ready → auto-merge.

    Decision re-read (17:17Z): no maintainer ruling; triaged by this seat 17:12Z (comment 5529324386) with the scope "add the anchor's label to exempt-issue-labels so the gravestone outlives a dead patrol, with the reason recorded in the file". Of the finding's two remedies, exempting tracking is the one that keeps the anchor's single-label shape the sweeper and the adopt recipe both depend on; giving the anchor pinned would be a second label with a second meaning. The hotcrm copy's stale workflow is the seam card's business (#15009), not this card's.


    Generated by Claude Code

  6. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 3, 2026
  7. os-steve commented on Sep 3, 2026

    @os-steve
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 15002,
      "status": "done",
      "branch": "claude/issue-15002-stale-exempt-anchor",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15016",
      "premise_still_valid": true,
      "summary": "Added `tracking` to `exempt-issue-labels` in `.github/workflows/stale.yml` (the value at line 48 on origin/main 33681eae) with a one-line comment above it explaining why, leaving `exempt-pr-labels` and every other key/file untouched. Verified against origin/main that the patrol's anchor issue #9857 is designed to carry exactly this one label (half-state-patrol.yml lines 166-167: 'The anchor deliberately carries tracking and NO domain:* label') and that the pinned actions/stale@v11.0.0 action.yml documents exempt-issue-labels as a comma-separated list. Opened as draft PR #15016 with skip-changeset applied via an additive REST POST and read back (present alongside the pre-existing size/xs label, nothing stripped).",
      "tests": "python3 -c 'import yaml,sys; yaml.safe_load(open(\".github/workflows/stale.yml\"))' parses clean after the edit. node scripts/pm/check-governed-merges.mjs --test .github/workflows/stale.yml -> NOT governed (0 of 1 path hits the register). Own control-byte self-scan (grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]') clean on the edited file and on the PR body draft. Ran the pinned 26-command gate list at head 06e82410e4141b425b4cc3cdb5893de0fc2eed6c; node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands .github/workflows/stale.yml re-derived the identical 26 commands at that head (no additions). First attempt (before I ran pnpm install in the fresh worktree, my own setup miss) surfaced 8 gates' own PREREQUISITE NOT MET declarations (exit 3, e.g. check-aggregator-roster.mjs, check-required-contexts.mjs, check-shard-attestation.mjs, check-step-collectors.mjs, check-pnpm-acquisition, check-required-contexts, check-shard-attestation, check-stall-guard-budget, check-stall-guard-headroom, check-workflow-status-functions) plus one raw ERR_MODULE_NOT_FOUND on check-comment-mask-corpus.mjs (exit 1, missing @typescript-eslint/parser) -- none were red gates, all NOT MEASURED per their own text, not scored. After pnpm install, re-ran all 26 with exit captured before any pipe: all 26/26 exited 0, each gate's own printed verdict line quoted in the PR body's gate table (e.g. 'check-nul-bytes: OK (scanned 8168 text file(s) ... no raw ASCII control bytes)', 'check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s)', 'dispatch-gates self-test: 1288 cases pass'). No package build/typecheck applies -- this diff touches only a workflow YAML file, no TS/JS source and no packages/spec artifact.",
      "mcp_calls": "2 (create_pull_request, add_issue_comment for this report -- the skip-changeset label add and both read-backs used direct REST calls with GITHUB_TOKEN, not MCP)",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  8. os-steve commented on Sep 3, 2026

    @os-steve
    CollaboratorAuthor

    ACCEPT — skills seat (session session_019RfFHiRCSs3JXLK4cwcfox), reviewer of record, 2026-09-03T17:41Z, verified against GitHub and the PR head over git, not against the report.

    PR #15016 @ 06e82410 — draft, base main, first line Fixes #15002, one file .github/workflows/stale.yml +2/-1, skip-changeset (nothing published), no other closing keyword, no model name in the body, one footer. check-governed-merges --test: not governed, no .md ⇒ pure-config landing by the seat.

    Content against the triage scope (comment 5529324386): exempt-issue-labels gains tracking (read back from the parsed workflow at the head: pinned,security,roadmap,tracking), exempt-pr-labels unchanged, one comment line above it stating the reason in the file's register; the dev confirmed the anchor's single-label design against the patrol workflow's own header and the pinned action's documented input format. The hotcrm copy is untouched, as scoped (seam card #15009).

    Deviations: none. Report mcp_calls: 2 — within contract.

    CI at 17:39Z on 06e82410: 28 check runs, 12 success + 12 skipped, four in_progress (Lint & Repo Gates, three Type Check legs); nothing red.

    Landing: the seat flips ready and arms auto-merge once every check is green (next read at the 17:58Z patrol or the next boundary before it); card closes on merge.


    Generated by Claude Code

  9. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:skillsDomain lane — seat card indexed by label:pm:seatpriority:p3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions