Skip to content

refactor(tools): remove the skill limits shell fallback #847

Open

Description

@tbhb-agent

Problem

The Go checker from #971 and its parity suite from #972 are on origin/main (591fd70), but nothing calls the checker: internal/agentrepo/command.go registers no check-skill-limits command, and tools/skill-limits.sh:39 still holds the active shell algorithm. #938 supplies the registration and a wrapper that probes for the command and otherwise runs the frozen tools/skill-limits-legacy.sh. That fallback keeps two implementations of one gate, and internal/skills/parity_unix_test.go (reads the legacy script at line 142, runs Bash at line 155, requires active and frozen scripts to match at line 312) cannot survive its deletion.

This issue ends the fallback once the N-2 window in docs/compatibility.md allows it. Part of #588. Follows #808 and #938.

Acceptance criteria

  1. AC1: tools/skill-limits.sh runs the Go check-skill-limits command with the caller's arguments and relays its standard output, standard error and exit status unchanged; tools/skill-limits-legacy.sh no longer exists and no capability probe remains in the entry point.
  2. AC2: A caller whose agentrepo lacks the subcommand exits nonzero with a minimum-version message on standard error and nothing on standard output. Dropped: an older binary cannot print a diagnostic it does not contain, and the supported invocation (AC5) never meets one, so no new version logic is added (Replace complex shell with tested Go, starting with gates and merging #588 freeze).
  3. AC3: The existing parity cases pass against the Go path alone. Dropped: those cases compare against the deleted shell. AC7 and AC8 keep the same behavior as Go-only expectations.
  4. AC4: The change alters no generated skill file and mise run check-generated-skills passes.
  5. AC5: mise run lint-skills and tools/skill-limits.sh check skills with the repository's own build, so an agentrepo on PATH, of any version, is never run.
  6. AC6: When mise is not available, tools/skill-limits.sh exits nonzero with nothing on standard output and a message on standard error.
  7. AC7: For every case in the retained corpus (name lengths 63, 64, 65; description lengths 1023, 1024, 1025; uppercase and punctuation; each reserved name; folded, single-line and missing frontmatter; XML-like text; non-ASCII text at both boundaries; null bytes; an unreadable manifest after a valid one; invalid UTF-8), the command's standard output, standard error and exit status equal the expectations recorded from the frozen shell, with exactly two documented differences: the empty standard error for invalid UTF-8 (feat(skills): add the Go skill limits checker #971 A5), and the absence of Bash's ignored null byte in input warning lines, which the recording drops (test(skills): compare the Go checker with the frozen shell checker #972 B2a).
  8. AC8: With no targets the command checks packaged and repository-local skills and never deployed mirrors; with explicit targets it checks them in the order given and skips a missing manifest; -- before the targets is accepted.
  9. AC9: Non-functional: once the repository build is provisioned (toolchain and module cache present), a run of tools/skill-limits.sh leaves repository bytes, the index and refs unchanged and makes no network connection.
  10. AC10: Non-functional: the corpus gives the same verdicts and diagnostics on Linux, macOS and Windows, and under differing locales.
  11. AC11: For every exit status N from 0 to 255 that the Go command returns, tools/skill-limits.sh exits with N after one invocation, relays the command's standard error unchanged and runs nothing else.
  12. AC12: The change adds no new shell logic: no added line in any .sh file other than the single exec of the entry point, and no remaining shell body that parses manifests or chooses between implementations.
  13. AC13: The change merges only after the first release containing agentrepo check-skill-limits and at least two later releases are tagged, and the pull request cites them with dates.
  14. AC14: docs/script-inventory.md records the Go destination for tools/skill-limits.sh, and the migration note names the first release carrying the command and states that no consumer binary floor changes.

Scope and non-goals

Delete tools/skill-limits-legacy.sh, the capability probe in tools/skill-limits.sh, and the shell comparison in internal/skills/parity_unix_test.go with its Bash helpers and fake python3. Retain the behavioral corpus as Go-only tests whose expectations are recorded from the frozen shell before it is deleted. Update the two documents in AC14.

Non-goal: porting any other script. Non-goal: removing a fallback owned by another port, including #846. Non-goal: raising the agentrepo floor of the shared skill loader (DT_AGENTREPO_MIN_VERSION). Non-goal: changing the repository launcher, which belongs to the launcher prerequisite below.

Approach

Verified on origin/main: tools/skill-limits.sh is called only by mise.toml:439 (lint-skills) and is outside the vendored payload under .devtools/. tools/check-versions.sh:4 forwards with exec mise --quiet run run agentrepo check-versions "$@", and the shim here takes the same shape: one exec of the repository launcher, so a binary built from the same tree always runs and no older binary needs to diagnose itself. AC2 is dropped for that reason, and the absent case is covered by AC6.

The launcher does not satisfy AC11 today. [tasks.run] (mise.toml:193-202) ends in go run, and go run reports a failed child as exit status N on standard error and exits 1, so a status of 3 reaches the caller as 1. Probed on 2026-10-31 with a Go program exiting 3: go run returned 1 and printed exit status 3, while mise run of a task exiting 3 returned 3. The shim therefore needs a launcher that runs a built binary, such as the binary devtools:build writes to bin/ (.devtools/tasks/go.toml:10); the launcher change and its test live in the launcher prerequisite under Dependencies, not here. The shim's exact spelling is then chosen against that launcher.

On the branch head before the deletion, run the existing parity suite once to capture each corpus case's three streams and status as test data, with the null-byte warning lines dropped as parity_unix_test.go:174 does today, add command-level tests over that data, then delete the shell comparison. TestCorpus, TestDiscovery, TestExplicitTargets, TestLocaleIndependence, TestInvalidUTF8, TestUnreadableAfterValid and TestFilesystemOnly from #971 already run Go-only and stay. Rewrite the #938 Bats file to the AC5, AC6 and AC11 cases and drop the fallback cases. Do not add parity checks that need deleted shell.

Dependencies

Blocked by #808
Blocked by #333
Blocked by #544
Blocked on: #938

#808 supplies the port, #938 (a pull request, so a URL line rather than a native link) the registration, and #333 and #544 are the retirement prerequisites #808 names.

Two prerequisites are not yet owned by any issue. Each is to be filed and linked as a native blocker before work starts:

  • Title: docs(compat): announce the skill-limits fallback retirement. Scope: add to the migration note a statement that tools/skill-limits-legacy.sh and the probe in tools/skill-limits.sh are removed after the N-2 window, naming refactor(tools): remove the skill limits shell fallback #847, and ship it in a tagged release. That release must be at least two releases older than the one this change ships in, which starts the warning period docs/compatibility.md:5 requires.
  • Title: fix(tools): forward the child's exit status from the repository launcher. Scope: make mise run run <bin> ... return the exact exit status of the Go command with its standard error relayed unchanged, tested through the real launch path with a child that exits with several statuses, using tested Go or the existing build task and no new shell logic.

Release-window evidence on 2026-10-31: the newest tag is v0.23.0 (2026-10-29), and git grep check-skill-limits v0.23.0 -- internal finds nothing, so no release carries the command yet. Recent tags are v0.20.0 (2026-10-25), v0.21.0 (2026-10-25), v0.22.0 and v0.22.1 (2026-10-26). Let X be the first tag whose internal/agentrepo/command.go registers the command. The pull request needs tags X, X+1 and X+2, and the announcement above in a release at least two releases older than the merge.

Deferred review findings from the pull request become issues with a Problem section and numbered ACs, are listed under Related, and any code that defers work links its issue. #973 tests the fake python3, which this change deletes, so close it with that reason.

Size

S. About 200 added and 600 deleted lines across the legacy script, the parity suite and the Bats file; the launcher change is excluded and sits in its own prerequisite. If the changed-line count exceeds 800, split the recorded-transcript tests into a preceding pull request that adds them while the shell still exists.

Verify plan

  • AC1: git grep -n -E 'skill-limits-legacy|command -v|agentrepo' -- tools/skill-limits.sh returns nothing but the one launcher line, git grep -n 'skill-limits-legacy' returns nothing outside docs/ history and the PR text, and tools/skill-limits.sh has one non-comment line. Unrelated probes such as packages/agents/source/lib/lib.sh:149 are out of scope. A Bats case also runs the shim over a clean and a failing fixture and compares both streams with the Go command's.
  • AC4: mise run generate-skills leaves no diff, then mise run check-generated-skills passes.
  • AC5: a Bats case puts a stub agentrepo that records any call first on PATH, runs mise run lint-skills, and asserts the record is empty and the verdict matches.
  • AC6: a Bats case runs the script with a PATH lacking mise and asserts nonzero, empty standard output and a standard error message.
  • AC7: a new Go test compares the command's streams and status with the recorded data for each corpus case, including the null-byte cases at internal/skills/check_test.go:49 and :53, and asserts the two documented differences and no others.
  • AC8: TestDiscovery and TestExplicitTargets plus a command-level case for --.
  • AC9: a Bats case provisions the build first, then runs tools/skill-limits.sh and mise run lint-skills with GOPROXY=off, MISE_OFFLINE=1 and proxy variables pointing at a listener that counts connections, and asserts zero connections and identical repository bytes, index and refs before and after. TestFilesystemOnly additionally covers skills.Check.
  • AC10: go test ./internal/skills passes in the test (ubuntu-24.04), test (ubuntu-24.04-arm), test (macos-15) and test (windows-2025) jobs, and TestLocaleIndependence covers locales.
  • AC11: a Bats case runs the shim through the real launcher against a test command that exits with 0, 1, 2, 3 and 255, and asserts each status, one invocation (a counter file) and unchanged standard error. A stub mise is not used for this case.
  • AC12: git diff origin/main -- '*.sh' shows no added line besides the exec, and git grep -n -E 'if |case |for ' tools/skill-limits.sh returns nothing.
  • AC13: the pull request's Verification lists git tag --contains output for the registering commit, the three tags with dates, and the announcement.
  • AC14: git grep -n skill-limits docs/script-inventory.md docs/agent-skills-migration.md shows the updated text.
  • Definition of done: D1 each AC is verified at the merged head, with the commands and results cited in the pull request's Verification. D2 an independent review approves the exact merged head, and every finding has a recorded disposition. D3 required checks pass with no gate loosened. D4 the leak scans the repository specifies are clean, and every post made for this work passes the per-post leak review before it is posted. D5 every commit, including the squash, is signed. D6 the documentation matches the change (AC14). D7 every commit, including the squash, has an Assisted-by trailer and the pull request has the provenance:agent-assisted label. D8 the pull request stays within the size limits. D9 follow-ups and deferred findings are filed as issues with a Problem section and numbered ACs, listed under Related, and linked from any code that defers them. D10 the merge closes the issue through a closing reference, the board shows Done, and the final Status: comment names the merge commit.

Risks

A consumer or script that calls tools/skill-limits.sh with its own agentrepo now runs the repository build instead; no caller outside mise.toml exists on origin/main. The release window can stay open for weeks, which only delays the work. If the launcher prerequisite slips, AC11 cannot pass and the change waits rather than shipping a shim that turns every failure into status 1. Undo: revert the change, which restores the shim from #938 and the frozen script.

Operator authority

None.

Activity

  1. added
    change:chorePR with no user-facing change (chore, refactor, build, ci, test, style, perf)
    on Oct 27, 2026
  2. added theissue type on Oct 27, 2026
  3. tbhb-dev-reviews commented on Oct 31, 2026

    @tbhb-dev-reviews

    Refinement: NOT READY — release blockers and the Go-only verification contract need clarification

    Checked

    Gaps

    1. Native dependencies and release eligibility. The blocked-by endpoint returns no dependencies. Record the port prerequisite through native blocked-by, with refactor(tools): move skill limit checks to Go #808 tracking the port and feat(skills): route checks through the Go command #938 supplying remaining integration. Reconcile refactor(tools): move skill limit checks to Go #808's explicit Ship the Go binaries as release assets installed through mise's github backend #333/feat(distribution): migrate consumer tool pins to release assets #544 retirement prerequisites with this issue's generic release evidence. Track the release/window condition and require evidence of the first capability-bearing release, supported N/N-1/N-2 versions, and the warning period in docs/compatibility.md:3 and docs/compatibility.md:5. Raising the documented floor alone does not establish eligibility. Current main still lacks command registration at internal/agentrepo/command.go:85.
    2. AC2 versus an exec-only shim. Deleting the probe and directly executing an older binary cannot make that binary invent a minimum-version diagnostic. The current command runner prints the error it receives at internal/agentrepo/command.go:106. Define the supported invocation boundary that guarantees the diagnostic, and cover the absent-binary case. Make that contract achievable through tested Go with at most a thin exec shim. Adding shell version checks or diagnostic logic would conflict with Replace complex shell with tested Go, starting with gates and merging #588's freeze. Keep observable results in ACs and implementation decisions in Approach.
    3. AC3 cannot run unchanged after deletion. internal/skills/parity_unix_test.go:142 reads the legacy script, internal/skills/parity_unix_test.go:155 runs Bash, and internal/skills/parity_unix_test.go:312 requires equality between active and frozen scripts. Specify retaining the behavioral corpus and expected findings/streams/status in Go-only regression coverage while retiring shell comparisons and dependent fixtures. Explicitly include tools/skill-limits-legacy.sh in the removal scope; current main also retains the active algorithm at tools/skill-limits.sh:39.
    4. Retained non-functional and caller contract. Add explicit ACs and Verify rows for unchanged supported arguments/target selection, findings/order/totals/status, offline and read-only execution, deterministic supported-platform results, and supported-command failure propagation without fallback or repeat. These are promised in refactor(tools): move skill limit checks to Go #808 but are not defined by “existing parity cases pass” once those cases change. Preserve the deliberate stderr differences documented in merged feat(skills): add the Go skill limits checker #971/test(skills): compare the Go checker with the frozen shell checker #972. Name focused verification checks. AC4's generation check does not cover these properties.
    5. Complete D9. Include deferred findings, the minimum follow-up issue content (Problem and numbered ACs), and links from deferred work left in code, alongside the existing Related requirement.

    Split

    Keep one XS retirement PR after the port and release/window blockers clear. Command integration belongs to #938 and distribution work to its existing issues; no additional implementation split is needed.

    Definition of done

    Ticks mark coverage promised by the issue's ACs and Verify plan, not implementation already completed.

    • D1 Each AC verified on the merged head with command/result or linked evidence in Verification; repair AC3's contradictory plan above.
    • D2 Independent exact-head review and recorded disposition of findings.
    • D3 Required checks pass with no gate loosened.
    • D4 Repository leak scans and per-post leak review required.
    • D5 Every commit, including the squash, signed.
    • D6 Documentation matches or Verification explains no impact; scope includes the minimum-version migration note.
    • D7 Every commit, including the squash, has Assisted-by and the PR has provenance:agent-assisted.
    • D8 One XS PR within size limits.
    • D9 Follow-ups and deferred findings have issues with Problem and numbered ACs, Related links, and issue links in deferred code.
    • D10 Closing reference, board Done, and final Status comment naming the merge commit.

    Round: 1 of 3

  4. tbhb-dev-reviews commented on Oct 31, 2026

    @tbhb-dev-reviews

    Refinement: NOT READY — native blockers and the replacement invocation still need repair

    Checked

    Gaps

    1. Round 1 native-dependency gap remains unresolved. The live blocked-by endpoint still returns an empty list. Body text does not create native relationships. Add native blockers for refactor(tools): move skill limit checks to Go #808, feat(skills): route checks through the Go command #938, Ship the Go binaries as release assets installed through mise's github backend #333 and feat(distribution): migrate consumer tool pins to release assets #544. Their rationale is now reconciled with refactor(tools): move skill limit checks to Go #808, and AC13 plus docs/compatibility.md:3 and docs/compatibility.md:5 define release/warning evidence, but the announcement prerequisite is explicitly still unfiled. File and link that issue and its native dependency rather than leaving the warning period unowned. The v0.23.0 tree contains no check-skill-limits registration; the capability release window has not started.

    2. The proposed invocation does not establish AC11. tools/check-versions.sh:4 forwards through mise's run task, which executes Go via go run at mise.toml:201. That launcher reports a failed child and returns its own failure status rather than forwarding arbitrary child statuses such as 3. A stub mise exiting 3 bypasses this real path and cannot prove the Go command's status survives. Define and verify the real launcher contract, including failure stderr, using a failing child through the actual launch path. Keep launcher behavior in tested Go or existing tooling with only the permitted exec shim. State observable forwarding behavior in AC1 and keep exact launch spelling in Approach.

    3. AC7 contradicts a second documented stderr difference. The retained corpus includes null-byte cases at internal/skills/check_test.go:49 and internal/skills/check_test.go:53. internal/skills/parity_unix_test.go:174 removes Bash's “ignored null byte in input” warnings before comparing stderr; merged test(skills): compare the Go checker with the frozen shell checker #972 documents this as B2a. AC7 permits only the invalid-UTF-8 exception. Include null-byte warning normalization in the recorded-expectation contract and Verify row, preserving existing Go behavior. This remains part of Round 1 gap 4.

    4. Verify needs scoped checks and a consistent offline boundary. AC1's repository-wide grep cannot return nothing: unrelated probes remain at packages/agents/source/lib/lib.sh:149 and packages/agents/source/skills/fix-pr/shared/scripts/diagnose.sh:5. Scope the probe assertion to the skill-limits entry point and check legacy references separately. For AC9, TestFilesystemOnly calls skills.Check directly at internal/skills/check_test.go:258; it does not exercise mise, the launcher or RunCheck's Git lookup at internal/skills/check.go:28. The proposed command-level snapshot checks mutation but supplies no network assertion. State whether offline/read-only covers the provisioned checker or the whole supported wrapper, then verify that same boundary with network denial/traps and repository-state checks. Record any provisioning prerequisite explicitly.

    Split

    Keep one S retirement PR after its native prerequisites and release/warning window clear. File the described announcement issue before work starts. Integration stays in #938 and distribution in #333/#544. If transcript retention would reach 800 changed code lines, land those Go expectations in a preceding PR while the reference exists; keep each PR under 800.

    Definition of done

    Ticks mark coverage promised by the ACs and Verify plan, not completed implementation. D1 remains open because current verification promises conflict with origin/main.

    • D1 Each AC verified on the merged head, with each ID and command/result or linked evidence in PR Verification; repair gaps 2–4.
    • D2 Independent approval of the exact merged head and recorded disposition of findings.
    • D3 Required checks pass with no gate loosened.
    • D4 Repository leak scans and per-post leak review required.
    • D5 Every commit, including the squash, signed.
    • D6 Documentation matches the change; AC14 names the inventory and migration note.
    • D7 Commit and squash Assisted-by trailers and provenance:agent-assisted PR label.
    • D8 One PR within size limits, with a preceding transcript-test PR if necessary.
    • D9 Follow-ups and deferred findings require issues with Problem and numbered ACs, Related links, and issue links in deferred code. The announcement issue still needs filing as gap 1 states.
    • D10 Closing reference, board Done, and final Status comment naming the merge commit.

    Round: 2 of 3

  5. tbhb-dev-reviews commented on Oct 31, 2026

    @tbhb-dev-reviews

    Refinement: NOT READY — two prerequisites remain unowned and verification leaves contract gaps

    Checked

    Gaps

    1. The new blockers are still proposals rather than owned dependencies. Dependencies explicitly says the warning announcement and real-launcher repair are not owned by any issue. File and refine those bounded prerequisites, then attach their native blocked-by relationships before this item is Ready. Existing native blockers are now correct, but neither refactor(tools): move skill limit checks to Go #808 nor Ship the Go binaries as release assets installed through mise's github backend #333/feat(distribution): migrate consumer tool pins to release assets #544 owns these additional outcomes. The launcher is presently go run at mise.toml:201; the build task writes under bin/ at .devtools/tasks/go.toml:10. The newest local tag, v0.23.0, contains no check-skill-limits registration. AC13 appropriately waits for the command-bearing releases, but the separate two-release removal warning required by docs/compatibility.md:5 still needs its filed owner. A promise to file blockers after refinement does not establish implementation readiness.

    2. AC9's network assertion does not prove its stated boundary. GOPROXY=off, MISE_OFFLINE=1 and proxy variables plus a zero-count proxy listener only observe clients that honor those settings. Direct connections, including subprocess traffic outside that proxy, can bypass the listener. Specify network denial or observation covering the whole wrapper/launcher process tree, and retain the repository/index/ref snapshot. State explicitly whether provisioning includes an already-built binary and other launcher outputs: AC9 currently names only the toolchain/module cache, while Verify provisions the build first. That distinction matters if the launcher creates bin/ during the measured run. TestFilesystemOnly at internal/skills/check_test.go:232 exercises Check directly and cannot establish the wrapper contract.

    3. AC11's universal status promise has only five samples. The AC requires every status from 0 through 255, while Verify checks only 0, 1, 2, 3 and 255. Exercise all 256 through the real launcher with the stated stderr and one-invocation assertions, including statuses such as 126, 127 and 128; alternatively narrow the observable contract to the supported status domain and justify it. The launcher prerequisite must supply the same testable boundary. Keep this in Go/integration coverage without adding shell decision logic.

    Split

    Keep retirement as one S PR after the announcement and launcher prerequisites are filed, refined and natively linked, and the release/warning conditions clear. Keep launcher implementation outside this PR. The estimate of about 200 added plus 600 deleted lines reaches the hard boundary: split recorded-transcript coverage into a preceding PR if retirement would reach 800 changed code lines, so each PR stays strictly below 800. After this third NOT READY, the owner should rethink the item around those explicit prerequisite issues rather than start another refinement round on the same unresolved scope. No operator decision is needed.

    Definition of done

    Ticks mark duties covered by the issue's ACs and Verify plan, not completed implementation. D1 remains open because the proposed checks do not yet establish the complete AC9/AC11 contracts.

    • D1 Verify each AC on the merged head and cite each ID with command/result or linked evidence in PR Verification; close gaps 2 and 3.
    • D2 Independent approval of the exact merged head and recorded disposition of every finding.
    • D3 Required checks pass without gate loosening.
    • D4 Repository leak scans and per-post leak review come back clean.
    • D5 Each commit, including the squash, is signed.
    • D6 Documentation matches the change; AC14 covers the inventory and migration note.
    • D7 Commit and squash Assisted-by trailers and the provenance:agent-assisted PR label.
    • D8 One bounded PR within size limits, with preceding transcript coverage split out when necessary.
    • D9 Follow-ups and deferred findings have issues with Problem and numbered ACs, appear under Related, and deferred code links its issue. The unfiled prerequisites remain gap 1.
    • D10 Closing reference, board Done, and final Status comment naming the merge commit.

    Round: 3 of 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    change:chorePR with no user-facing change (chore, refactor, build, ci, test, style, perf)refinement:activeA refinement round is open; not ready for implementation

    Type

    Fields

    Priority

    None yet

    Work type

    None yet

    Projects

    • Status
      Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions