feat(skill-quality): add the shared listing-budget report, fix check 2's joiner - #1450
Conversation
…2's joiner (#1404) check-skill.sh check 2 only ever guarded the per-skill listing-entry cap (skillListingMaxDescChars, 1536 chars); the shared budget every loaded skill draws from together (skillListingBudgetFraction, default 1% of the model's context window) had no check at all. New check-listing-budget.sh pools one or more skills roots into a single aggregate estimate against a documented, overridable default (the harness's own SLASH_COMMAND_TOOL_CHAR_BUDGET fallback of 8000 chars), reports the biggest contributors on overflow, and never hardcodes a resolved live value (context window and skillListingBudgetFraction are both consumer settings this static check cannot observe) — always advisory, exit 0. Wired into the skill-quality-gate CI job as a report-only step pooling every plugin's skills/ root into one marketplace-wide aggregate. Also fixes check 2 itself: the harness assembles a listing entry as description + " - " + when_to_use (a literal 3-char joiner) that check 2's sum omitted, under-counting by 3 whenever when_to_use is populated. Settles the discrepancy the issue raised between two derivations of the budget formula: fetched current docs confirm SLASH_COMMAND_TOOL_CHAR_BUDGET's 8000-char fallback is exactly contextTokens(200000) x ~4 chars/token x skillListingBudgetFraction(0.01) — the contextTokens x 4 x fraction derivation, not a bare tokens x fraction reading. Item 3 (amending check 12 to accept a populated when_to_use as satisfying the trigger-spec requirement) is deferred per the issue's own sequencing note: PR #1096 is an open, active PR already editing check-skill.sh (claims check 21). #1404 stays open to track it. Refs #1404 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb2f8c26ba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…catalog sync Two things the local test/lint pass didn't catch before push, both flagged by this repo's own CI gates on the first run: - The two new scripts (check-listing-budget.sh, check-listing-budget.test.sh) were committed without the executable bit — the hygiene lane's shebang-vs-mode check caught it. - The root README.md's generated catalog block was stale against the bumped skill-quality plugin.json description — regenerated via scripts/generate-catalog.mjs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 49s —— View job PR Review
Overall assessmentThe joiner fix is correct and well-tested. The new Findings1. Report header reports arg count, not found-root countprintf 'Shared listing-budget estimate over %d skill(s) across %d root(s):\n' "$ENTRY_COUNT" "${#ROOTS[@]}"
The same skip-vs-count gap means the Codex P2 concern (error on a missing explicit root) and this header inaccuracy have the same root: 2.
|
|
Claude finished @kyle-sexton's task in 2m 50s —— View job Security ReviewNo security issues found. Reviewed all 10 changed files with focus on injection surfaces, GitHub Actions patterns, secrets handling, and path/command injection. GitHub Actions workflow (
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 041e5f6f6e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Automated review on #1450 caught it: the usage() sed range (2,79p) clipped the header's final line ("in the issue this script closes.") since the comment block actually runs through line 80. 2,80p now captures the whole header. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
This was generated by AI during work-loop execution. Addressed the automated review's minor finding ( Security review found no issues; no other findings to address. |
|
Claude finished @kyle-sexton's task in 1m 57s —— View job PR Re-Review (post b2d3f7f)
Sed fix: confirmed correct
Open findings (unchanged from prior review)The following were not addressed in b2d3f7f and remain open: 1. Header reports arg count, not found-root count (my prior Finding 1)printf 'Shared listing-budget estimate over %d skill(s) across %d root(s):\n' "$ENTRY_COUNT" "${#ROOTS[@]}"
2. Budget source label misleading when
|
|
Claude finished @kyle-sexton's task in 2m 28s —— View job Security Review
No security issues found. The commit reviewed is GitHub Actions workflow (
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b2d3f7f82b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… inputs Addresses the five open Codex review findings on the new listing-budget reporter, all confirmed against branch content and current official docs. Exclude `disable-model-invocation: true` skills from the aggregate. The invocation-control table at https://code.claude.com/docs/en/skills records "Description not in context" for that frontmatter, and "Hide individual skills" states it "removes the skill from Claude's context entirely" — such a skill spends none of the shared description budget, so counting it overstated the report. On this marketplace: 183 skills / 109,205 chars -> 132 / 83,594, still ~10.4x over the 8000-char default, so the finding the check exists to surface is unchanged. Reject a missing explicit skills root as an environment error (exit 2) instead of silently skipping it, which omitted a whole plugin subtree from a falsely low "OK" aggregate. The no-args resolution path keeps its own "no skills root found" message. Report the count of roots actually scanned rather than the count of arguments given. Validate every numeric override as a positive number, integer or decimal, routing failures into the documented exit 2. A nonnumeric value previously either coerced to zero in awk — fabricating a zero-character budget and a bogus overflow WARN while still exiting 0 — or crashed with an undocumented exit 1. Give a fixed CHECK_SKILL_LISTING_BUDGET_CHARS precedence over the token/fraction reconstruction, as the script's own header always claimed, announcing the ignored reconstruction input rather than discarding it silently; and label it an override rather than the "documented default". The budget verdict stays advisory throughout: OK and WARN both exit 0. Only operator/environment errors use exit 2, which was already documented. Derive --help's range from the header block itself so editing that block can no longer clip or overrun the help text. Tests grow 8 -> 21 assertions, covering every path above; the three input-handling defects had shipped green precisely because none was asserted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Warning Automated review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-running the job, or pushing a new commit, will retry the review. |
|
Warning Automated security review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-running the job, or pushing a new commit, will retry the review. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d218d15db7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
This was generated by AI during work-loop execution. All open review findings are addressed in d218d15 — the three the re-review listed as remaining, plus the two further Codex P2s. Per-finding evidence is in each inline thread; summary here since the last top-level comment still reads "three items remain before merge".
On the P1 design question, which the prior review flagged as "worth confirming before committing" — confirmed against current official documentation rather than recall, per this repo's fresh-docs mandate. https://code.claude.com/docs/en/skills: the invocation-control table records "Description not in context" for Semantics deliberately unchanged. The budget verdict stays advisory: OK and WARN both exit 0. The new hard failures use exit 2, which was already the script's documented usage/env-error class — and one of them removes an undocumented exit 1 that a nonnumeric override could previously trigger. Nothing in this commit touches Verification on d218d15: |
…iltering `skill_frontmatter::field` returns the raw YAML scalar, so the exact-string `== "true"` filter missed spellings a YAML reader treats as the same boolean. Reproduced before fixing: a nine-skill fixture root covering the variants reported 7 listing-eligible skills instead of 2 — an inline `# comment`, a quoted value carrying a comment, `TRUE`, `True`, and a trailing-whitespace value all leaked through and were counted, re-inflating the aggregate with descriptions the harness keeps out of context. Normalize before comparing: strip a whitespace-preceded YAML comment, trim surrounding whitespace, strip one quote layer, and fold ASCII case. Scope deliberately bounded, and stated in the code: - Comment-stripping applies only to this boolean, never to `description` or `when_to_use`, where a whitespace-preceded `#` is content rather than a comment. Folding it there would silently truncate a description. - YAML 1.1's `yes` / `on` aliases are NOT folded. The documented spelling is `true`, and over-matching risks dropping a skill over a value the harness may read as a plain string. - This is a pragmatic normalizer for one known field, not a YAML parser. Audited the sibling call sites for the same class of gap: this was the only boolean compared against raw field output in the plugin. Every other `skill_frontmatter::field` consumer extracts free text, and `skill_frontmatter::metadata_field` already strips trailing comments internally, so the shared library needed no change — keeping the blast radius off `check-skill.sh`'s PASS/FAIL surface. Corrects the changelog's marketplace figure to 132 skills / 83,611 chars. The previously recorded 83,594 was measured before this PR's own SKILL.md description edit added 17 characters, and the figure now carries the commit it was measured at, per this repo's own measurement convention. Tests 21 -> 22; the new case fails against the pre-fix code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f4121042ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| local pattern='^[0-9]+$' | ||
| [[ "$kind" == "num" ]] && pattern='^([0-9]+(\.[0-9]+)?|\.[0-9]+)$' | ||
| if [[ ! "$val" =~ $pattern ]] || ! awk -v v="$val" 'BEGIN { exit (v > 0) ? 0 : 1 }'; then |
There was a problem hiding this comment.
Normalize accepted integer overrides to decimal
When an integer override is zero-padded, such as CHECK_SKILL_LISTING_BUDGET_CHARS=08, this validation accepts it but later Bash arithmetic and printf %d interpret it as octal. With a nonempty skills root, the reporter emits invalid-octal diagnostics, renders the budget as 0, incorrectly reports OK, and exits 0; CHECK_SKILL_LISTING_MAX_DESC_CHARS=08 similarly bypasses the requested cap. Convert accepted integer values explicitly to base 10 or reject leading zeroes before using them.
Useful? React with 👍 / 👎.
| desc="$(skill_frontmatter::strip_quotes "$(skill_frontmatter::field description <<<"$fm")")" | ||
| wtu="$(skill_frontmatter::strip_quotes "$(skill_frontmatter::field when_to_use <<<"$fm")")" |
There was a problem hiding this comment.
Strip YAML comments before measuring listing scalars
When valid frontmatter uses an inline YAML comment outside the scalar, for example description: "12345" # maintainer note, skill_frontmatter::field returns the comment and strip_quotes can no longer remove the surrounding quotes. The reporter therefore counts the source comment and quoting rather than the five-character value Claude loads; a fixture with commented description and when_to_use values was reported as 50 characters instead of 13, which can produce false overflow warnings and contributor sizes. Parse or quote-aware-strip YAML comments before measuring these fields.
Useful? React with 👍 / 👎.
Main shipped skill-quality 0.11.0 (#1450, the shared listing-budget reporter) while this branch also claimed 0.11.0. Both sides are composed across six conflicts; nothing is blanket-picked. - .claude-plugin/plugin.json: recomputed to 0.12.0 above main's release. The description takes main's shape — the listing-budget reporter, and check 2 renamed to the per-skill listing-entry cap — plus this branch's twenty-one count and fresh-eyes item. A shared version would have silenced changelog-parity entirely: its bump gate short-circuits when head equals base. - CHANGELOG.md: this branch's section becomes 0.12.0 and main's 0.11.0 section is restored verbatim beneath it. - skills/check/SKILL.md: main's frontmatter, which carries the listing-budget action and its two new trigger phrases, with the check count and fresh-eyes item re-applied. Preserving those phrases is what keeps check 3 green. - README.md and plugins/skill-quality/README.md: main's rewrites naming the listing-budget reporter, with this branch's counts restored; the more detailed of the two injection-portability bullets is kept over main's terser duplicate. - scripts/check-skill.test.sh: purely additive on both sides — this branch's check-21 fixtures and main's joiner-boundary test. Git placed the shared trailing `fi` outside the conflict region, so it is restored explicitly. Main's check-skill.sh changes (check 2's 3-char joiner, header comments) merged without conflict and do not touch check 21. Verified post-merge: 81 assertions pass — 80 before the merge plus main's one added test, which is the arithmetic confirming the test-file splice lost nothing. Main's check-listing-budget.test.sh passes. check 2 now measures 1014/1536 for the longer composed description under its new joiner arithmetic, and all 7 base-ref trigger phrases are preserved. Comparing this branch's scanner before and after the ambiguity suppression over the SAME merged tree gives 166 records either way, byte-identical, so suppression still changes nothing on real content.
#1494) ## Summary `plugins/skill-quality/.claude-plugin/plugin.json` carried **two** `version` members on `main`: ```text 4: "version": "0.11.0", 5: "version": "0.12.0", ``` The advertised release was therefore parser-dependent — last-wins readers (`jq`, Python `json.load`) resolve `0.12.0`, first-wins readers retain `0.11.0`, and duplicate-rejecting consumers can reject the manifest outright. Any of those can misidentify or block a plugin update. **Why `0.12.0` is correct.** `main` carried `0.11.0` before #1096, released in #1450, and this plugin's CHANGELOG has `## [0.12.0]` above main's `## [0.11.0]`. The `0.11.0` member is the stale one, and it is what this PR deletes. **Scope.** One file, one deletion. Nothing else in the manifest and nothing else in the repo. **How it happened.** Introduced while resolving a `main` merge in #1096. Both sides of that conflict carried `"version": "0.11.0"` — main had released 0.11.0 in #1450 and the branch also claimed 0.11.0 — so git treated the version line as **common context outside** the conflict region and placed only the differing `description` inside it. A scripted replacement of the conflict region then emitted a fresh `version` + `description` pair above the surviving common-context one. ## Test plan A duplicate-key-aware parse is the only check that observes this, and it is clean after the change: ```console $ python -c "... json.load(..., object_pairs_hook=<duplicate detector>) ..." duplicate keys: NONE version: 0.12.0 ``` Also confirmed: - `git diff --stat` is exactly `1 file changed, 1 deletion(-)`. - The CHANGELOG's top section is still `## [0.12.0]`, so the manifest and the release notes agree. - `changelog-parity-gate` and `plugin-gate` pass in CI on this branch. Worth flagging for reviewers: `json.load`, `jq`, and JSON Schema validation all resolve duplicate keys last-wins, so every one of them reports `0.12.0` whether or not the duplicate is present. That is why this defect passed a fully green 27-context suite on #1096, and it is why the fix is verified with a duplicate-key hook rather than a plain parse. ## Related - #1096 — the PR whose merge-conflict resolution introduced the duplicate member. - #1450 — released skill-quality `0.11.0` on `main`, the collision that produced the conflict shape. - #1493 — follow-up covering the gate gap: no CI check currently detects a duplicate key in a plugin manifest. Closes #1492
…1506) Closes #1498 ## Summary *This was generated by AI during work-loop execution.* No gate in CI could detect a duplicate key in a plugin/marketplace manifest. JSON Schema validates the already-parsed document (structurally blind to a repeated member), and every consumer downstream resolves it last-wins with no signal — the exact shape that shipped `plugins/skill-quality/.claude-plugin/plugin.json` with two `"version"` members through a fully green 27-context suite (#1492). ## Fix Added `scripts/check-manifest-duplicate-keys.py`: reads each `plugins/*/.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json` via `json.loads(text, object_pairs_hook=...)`, intercepting the raw key/value pairs of every JSON object literal (at any nesting depth) *before* the standard de-duplicating collapse into a dict. A duplicate is recorded (not raised) so the parse still completes last-wins — identical to how `json.load`/`JSON.parse`/`jq` resolve it — and every distinct duplicate key in a file is reported in one pass. Wired into the `hygiene` job in `.github/workflows/ci.yml`, directly adjacent to the existing "Validate plugin manifests" step (per the issue's own routing rationale — same file scope, same tier as "does it parse"/"does it match the schema"), gated the same way the four `check-jsonschema` steps are (skipped only on a provably docs-only diff), and its outcome is added as one line to `scripts/aggregate-hygiene-results.sh`'s existing `CHECK_RESULTS` block so a duplicate key fails the job exactly like any other hygiene check. ## Verification - `bash scripts/check-manifest-duplicate-keys.test.sh` — 17 unit/CLI tests, all passing (nested-object duplicates, sibling-object non-duplicates, multiple distinct duplicate keys in one file, malformed JSON *not* double-reported, default-discovery glob covering both `plugin.json` and `marketplace.json`, missing-file handling, exit codes). - `test_catches_the_1492_shaped_duplicate_version_key` reproduces the actual #1492 defect shape (two `"version"` members) as an inline fixture and asserts the gate fails on it — the fixture proving the gate catches a real duplicate key, per the issue's own ask. - Live empirical cross-check against the two claims in the issue's verification table, run locally against a copy of the real (now-fixed) `plugins/skill-quality/.claude-plugin/plugin.json` with the duplicate `"version"` member re-injected: - `check-jsonschema --schemafile https://json.schemastore.org/claude-code-plugin-manifest.json <file>` → `ok -- validation done`, exit 0 (confirms schema validation is blind to it). - `python3 scripts/check-manifest-duplicate-keys.py <file>` → `DUPLICATE KEY: ... defines 'version' more than once ...`, exit 1 (confirms the new gate catches exactly what schema validation cannot). - `python3 scripts/check-manifest-duplicate-keys.py` (default discovery, no args) against the actual repo tree → `No duplicate JSON object keys found in 62 manifest file(s).`, exit 0 — no false positives on the real manifest set. - `actionlint .github/workflows/ci.yml` — clean. - `shellcheck --rcfile=.shellcheckrc scripts/check-manifest-duplicate-keys.test.sh` — clean. - YAML parse + step-order check of the modified `hygiene` job — new steps land in the intended position, ids wired correctly into `CHECK_RESULTS`. - `typos --config _typos.toml` over the new/changed files — clean. - No `plugins/*` manifest touched, so the per-plugin CHANGELOG-parity gate does not apply to this change. ## Related - #1492 — the shipped duplicate-key instance this gate would have caught. - #1494 — the manifest fix for #1492. - #1096, #1450 — the version-collision conflict shape that produces this defect class. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This was generated by AI during work-loop execution.
Summary
check-listing-budget.sh+ alisting-budgetaction — the shared/aggregate skill-listingcharacter budget (
skillListingBudgetFraction, default 1% of the model's context window) had nocheck at all.
check-skill.shcheck 2 only ever guarded the per-skill entry cap(
skillListingMaxDescChars, 1536 chars) — a different, narrower limit. The new script pools one ormore skills roots into a single aggregate estimate against a documented, overridable default (8000
chars — the harness's own
SLASH_COMMAND_TOOL_CHAR_BUDGETfallback), reports the biggestcontributors on overflow, and is always advisory (exit 0): the live budget depends on a model's
context window and a consumer's own settings, neither of which a static check can observe. Wired
into the
skill-quality-gateCI job as a report-only step pooling every plugin'sskills/root intoone marketplace-wide aggregate (currently ~109,000 chars against the 8,000-char default floor).
description+" - "+when_to_use— a literal 3-char joiner — that check 2's sum omitted, under-counting by 3whenever
when_to_useis populated. Not currently binding at present description lengths in thisrepo, but wrong in exactly the direction the listing-budget work is about.
current docs (
code.claude.com/docs/en/settings, live HTML) confirmSLASH_COMMAND_TOOL_CHAR_BUDGET's documented 8000-char fallback is exactlycontextTokens(200000) x ~4 chars/token x skillListingBudgetFraction(0.01)— thecontextTokens x 4 x fractionderivationfrom the shipped binary, not the bare
tokens x fractionreading.skillListingBudgetFraction(default
0.01) andskillListingMaxDescChars(default1536, matching this repo's existingDESC_CHAR_CAP) were also confirmed directly from the live settings page.skillListingBudgetFraction's default as a resolved live value)is folded into the new script's design: every tunable (budget chars, context tokens, fraction,
chars/token, per-entry cap) is environment-overridable, and the default budget is anchored on the
one documented number (8000) rather than reconstructed from assumed inputs.
Deferred: item 3 (check 12 amendment) — split into #1451
Item 3 — amending check 12 to accept a populated, single-quoted
when_to_useas satisfying thetrigger-spec requirement instead of requiring a literal
Use when:prefix — is not in this PR.The issue itself sequences it behind #1096 ("already editing that file and claims check 21"), and I
verified #1096 is a real, currently open, active PR (branch
feat/fresh-eyes-delegation-doctrine-gate,708 additions) that does edit
check-skill.sh(appending check 21). Per that sequencing note, item 3is split out into a dedicated follow-up, #1451, scoped and ready to pick up once #1096 merges or is
otherwise resolved. This PR closes #1404 (items 1, 2, and 4 are complete; item 3's tracking now lives
in #1451, not in a reopened #1404).
Test plan
check-listing-budget.test.sh— 8 assertions (help, missing-root error, empty root,under-budget OK, forced-tiny-budget WARN, joiner counted, per-entry cap applied before summing,
multi-root pooling). All pass.
check-skill.test.sh— added assertion 29 (boundary case:desc(1500)+wtu(34)=1534passescheck 2 without the joiner fix, fails at
1537/1536with it). Full suite: 29/29 pass.scripts/check-changed-skills.test.sh(CI orchestrator self-test): 8/8 pass.scripts/check-skill-portability.test.sh: 17/17 pass;check-skill-portability.sh origin/mainreports no unexcused coupling tokens in the changed skill file.
check-skill.shagainst the editedskill-quality:checkskill itself: PASS, 0errors, 0 warnings, all 7 base-ref trigger phrases preserved (description rewrite kept every
existing single-quoted trigger verbatim and only added new ones).
check-changed-skills.sh origin/main(the actual CI gate step): PASS.shellcheck --rcfile=.shellcheckrcclean on both new scripts (fixed one SC2016 literal-backticknote and one SC2034 unused-variable warning during development).
actionlint .github/workflows/ci.yml: clean.markdownlint-cli2clean on the three edited Markdown files.check-listing-budget.sh plugins/*/skillsagainst this repo: 183 skills, 61 roots,aggregate 108,975 chars vs the 8,000-char default budget (~13.6x over) — reproduces the
overflow the issue's own measurement found, with concrete top contributors.
Related
Closes #1404 (items 1, 2, and 4 from that issue; item 3 is split into #1451, not resolved here).
Refs #1451 (new follow-up: item 3, check 12 amendment — sequenced behind #1096, ready to pick up once
it merges or is otherwise resolved).
Refs #1096 (open PR already editing
check-skill.sh; #1451 sequences behind it).Refs #1271 (the original listing-budget issue #1404 was extracted from).