Skip to content

merge queue: checking #783 on main (a859be2), stacked on #826 and #777 - #916

Closed
mergify[bot] wants to merge 9 commits into
mainfrom
mergify/merge-queue/eaa2922dde
Closed

mergify[bot] wants to merge 9 commits into
mainfrom
mergify/merge-queue/eaa2922dde

Conversation

@mergify

@mergify mergify Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

✨ Pull request #777 ahead in the queue was removed (reason: checks failed). The pull request #783 has been requeued. ✨

#783 is queued for merge on branch main (a859be2).

Stacked behind 2 pull requests queued ahead of this batch, not part of it. These checks run on a tip that also carries their commits, so a failure here can come from them as much as from #783.

Queued ahead of this batch:

This pull request has been created by Mergify to speculatively check the mergeability of #783.
You don't need to do anything. Mergify will close this pull request automatically when it is complete.

Required conditions of queue rule admin-bypass for merge:

  • check-success = test
  • check-success = lint
  • check-success = validate

Required conditions to stay in the queue:

---
checking_base_sha: 3bdafb8e526a124518f15565990aae8d15293f5e
previous_check_retries: []
previous_failed_batches: []
pull_requests:
  - number: 783
    scopes: []
scopes: []
...

EdbertChan and others added 9 commits September 24, 2026 15:52
…on by default

skill-usage-log was switched off unless CATSTACK_SKILL_USAGE_LOG=1, covered
only Claude's Skill tool, swallowed write errors, and wrote a private file no
report read. ~/.cache/catstack-skill-usage-log did not exist on this machine,
so no skill use was on record.

It now records one catstack.hook_event.v1 row per use (action skill_used,
reason = source, skill = name) in the shared events file, from all three
harnesses:

- Claude PreToolUse (Skill|Read|Bash) and UserPromptSubmit
- Cursor preToolUse and beforeSubmitPrompt
- Codex PreToolUse and UserPromptSubmit

Sources: skill_tool, read (a Read of a SKILL.md, including Cursor's
skills-cursor/), shell_read (cat/sed/head/tail/nl/less/more/bat on one, also
inside a Codex exec code string), slash (a prompt starting /<installed
skill>), mention (Codex $<installed skill>).

Backtest on recent real transcripts: Claude 10 uses counted, Cursor 708
(670 of 671 SKILL.md Read calls, 22 of 22 ReadFile), Codex 1; rejected
mentions were Glob/Grep patterns, Write/StrReplace content, Task/Agent
prompts, WebFetch URLs and rg searches.

Unreadable input writes skill_usage_unchecked plus catstack-hook-error.
report.py --skills prints uses per skill per harness, `no record` for an
installed skill never used, and exits 2 on unchecked runs.
CATSTACK_SKILL_USAGE_LOG=0 opts out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Change-Id: I4c41be3269cd80190cbabad6d973de15008065f3
The installer ended with two separate checks -- an import sweep of the
installed hook scripts, and a drift check on the links -- and neither could be
run on its own afterwards. Asking "are my hooks healthy right now?" meant
rerunning the whole installer.

This folds both into `engine/hooks/_runner/doctor.py`, adds the check that was
missing, and gives it one exit code. It lives beside the runner so the
standalone form is reachable exactly when the hooks are:

    python3 ~/.claude/hooks/_runner/doctor.py

Four checks, in the order a hook event travels: the runner opens; every hook
entry script opens and imports; one real run through the runner reaches a hook
and comes back; the links point at this checkout. Each reports pass, fail, or
unchecked -- never two outcomes. Exit 0 / 1 / 2.

The end-to-end check runs `_runner/probe_hook.py`, which prints one marker and
exits, so the run writes no real hook's state into the session, and its metrics
row goes to a temporary directory instead of the real log.

Evidence, install.sh against a sandbox HOME:

    --- hook doctor (rerun any time: python3 $HOME/.claude/hooks/_runner/doctor.py) ---
    [1/4] runner        PASS   .claude .cursor .codex
    [2/4] hooks         PASS   checked=238 unreadable=0 import-fail=0 slow=0
    [3/4] end-to-end    PASS   exit=0, marker returned, metrics row written (x3)
    [4/4] effective     PASS   skip: sandboxed HOME
    hook doctor: 4 pass, 0 fail, 0 unchecked
    INSTALL_EXIT=0

`scripts/install/smoke_installed_hooks.py` and its test are removed; their
behaviour and every one of their cases moved into the doctor and
`tests/test_hook_doctor.py`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Change-Id: I9eb25920fa18f16691b7228496619d0f266d0210
The installer's banner changed, and one test asserted the old one, so the
suite failed on the rename:

    AssertionError: '--- loading every installed hook script (import smoke) ---'
    not found in "install.sh: WARNING ...

The test now asserts the doctor's four checks and its summary line. Two new
ones cover what the rename was for: the installer prints the standalone
command, and it exits 5 for a hook the interpreter cannot open -- the case
`test -f` passes for the whole of.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Change-Id: I3090042b1baabf2d425aa0199c5f54c5547ca547
Change-Id: I169286abad22b1994c6132fbd4d66379e3323eca
…e only

The reminder was re-read to the user on every prompt for every outstanding
claim, including claims whose proof had already been pasted. There was no way
to turn it down: CATSTACK_HOOK_MODE_UNVERIFIED_TAG_LEDGER=off still printed
the full list at exit 0, because this hook is not on run_hook and nothing
reads the registry mode for it.

CATSTACK_UNVERIFIED_TAG_REMINDER is read in-hook through _flags/flags.py, the
one pattern proven to work here (wrong-check-reflect/detect.py:12-15, 191):

  stale  default, and what unset means: only claims already 3+ turns old
  all    the old behaviour
  off    no injection

Unset resolves to stale on purpose. A flag whose unset value is the old
behaviour changes nothing for the person who asked for less. reminder() at
:138 already computed that exact subset and threw it away at :145.

The gate is on the injection only. Emission and recording are untouched on
every setting, so `off` buys silence without emptying the ledger it is mined
from. A value nobody understands, and an .env candidate that cannot be read,
are both named on stderr instead of passing as "not set".

hooks.toml gains an enabled_by line naming the flag. Nothing reads that field
-- registry.py:15 is its only mention in the repo -- so it is documentation
beside the gate, not the gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Change-Id: I4f465761ee7f84489616ba1e83c54eb98ceefff0
@mergify mergify Bot closed this Sep 24, 2026
@mergify
mergify Bot deleted the mergify/merge-queue/eaa2922dde branch September 24, 2026 08:22
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