Skip to content

finding(ci): hook-selftests.yml hand-enumerates the self-tests it claims to be the standing caller for — a new matrix ships uncalled #6906

Description

@claude

Found while mirroring the origin/main enumeration guard for objectstack-ai/objectstack#13305 (objectui PR for that card); out of scope for it, filed rather than redesigned there.

The gap

.github/workflows/hook-selftests.yml names itself "The standing caller for .claude/hooks/*.selftest.sh". It is not one. Its steps are a hand-kept enumeration, so a new self-test ships uncalled until someone remembers to edit this file:

 - name: guard-main-checkout-bash self-testrun: .claude/hooks/guard-main-checkout-bash.selftest.sh
- name: guard-shared-stash self-testrun: .claude/hooks/guard-shared-stash.selftest.sh

objectstack's twin does not have this failure mode. Its collector in lint.ymldiscovers:

mapfile -t selftests <<(find .claude/hooks -type f -name '*.selftest.sh'| sort)if [ "${#selftests[@]}"-eq 0 ];thenecho"... DISCOVERED NOTHING under .claude/hooks/."exit 1 # verifying nothing is a failure, not a passfi

That shape adds a new matrix with no workflow edit, and it fails loudly if the search ever stops matching — so it cannot silently degrade to a no-op.

Measured

On the card's branch, adding guard-tree-enum.selftest.sh to both repos:

reporunner shapenew self-test picked up?
objectstackfind .claude/hooks -name '*.selftest.sh' in lint.ymlyes, automatically — 4 discovered, no workflow edit
objectuitwo hardcoded run: steps in hook-selftests.ymlno — had to be added by hand

Why this is worth a card rather than a shrug

This workflow's own header argues at length that it is a RUNNER whose matrices "are meant to grow in .claude/** WITHOUT this file being touched", and it removed per-step case counts specifically to keep that independence safe. The enumeration in its steps: is the remaining half of exactly the drift it set out to avoid: the independence holds for cases inside an existing matrix, and breaks for a new matrix file.

It also interacts with #6451 (guard-main-checkout.sh has no self-test at all): whoever writes that missing matrix must remember this file too, or their new self-test is added and never run. Distinct mechanism though — #6451 is a missing self-test FILE, this is the runner that would not call it. Not a duplicate.

Suggested fix

Port objectstack's collector, including its two load-bearing properties: the empty-discovery branch is RED (a step that verified nothing is not a pass), and the loop tolerates-and-collects so one red matrix does not hide the others. scripts/dependabot-merge-gate.mjs's OPTIONAL_CONTEXTS description of this workflow should stay count-free (it was de-counted on the #13305 branch for exactly this reason).

Unassigned and unlabelled — grading and domain:* are triage's.


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    ci/cddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:queuepriority:p2

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions