Add a scheduled tech debt burndown skill - #14095
Conversation
Co-authored-by: Copilot <[email protected]> Copilot-Session: 7db06537-cb57-48b9-b09c-e0ea393c3734
Co-authored-by: Copilot <[email protected]> Copilot-Session: 7db06537-cb57-48b9-b09c-e0ea393c3734
Co-authored-by: Copilot <[email protected]> Copilot-Session: 6dba8ad8-2a85-4cda-a057-cf91b58a158e
Also record what this run learned: the staticcheck backlog is entirely stylistic with no SA findings, and which test and lint failures are pre-existing on a clean tree so future runs don't chase them. Co-authored-by: Copilot <[email protected]> Copilot-Session: 6dba8ad8-2a85-4cda-a057-cf91b58a158e
There was a problem hiding this comment.
Pull request overview
Introduces a new tech-debt-burndown agent skill to enable incremental, reviewable tech-debt cleanup runs in the GitHub CLI repo, and includes an example “first run” cleanup that resolves staticcheck findings in gh alias import.
Changes:
- Add a new
.github/skills/tech-debt-burndown/SKILL.mddescribing the one-thing-per-run workflow, target selection tiers, validation, and stopping rules. - Add a persistent, repo-committed memory file at
.experiments/tech-debt-burndown/memory.mdto carry binding corrections across independent runs. - Remove
staticcheckQF1012 findings inpkg/cmd/alias/imports/import.goby switching fromWriteString(fmt.Sprintf(...))tofmt.Fprintf(...).
Show a summary per file
| File | Description |
|---|---|
| pkg/cmd/alias/imports/import.go | Mechanical output-building change to eliminate staticcheck QF1012 findings without changing user-facing output. |
| AGENTS.md | Adds a discoverability pointer to the new tech-debt burndown skill. |
| .github/skills/tech-debt-burndown/SKILL.md | New skill definition and runbook for small, verifiable tech-debt fixes. |
| .experiments/tech-debt-burndown/memory.md | New binding “standing corrections” file to reduce repeated mistakes and re-proposed targets across runs. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Lite
The precedence rule justified itself with a false premise: every entry so far was written by an agent run, not a human. Justify precedence by specificity instead, and tell runs to re-verify factual claims rather than trusting them wholesale. Co-authored-by: Copilot <[email protected]> Copilot-Session: 6dba8ad8-2a85-4cda-a057-cf91b58a158e
Co-authored-by: Copilot <[email protected]> Copilot-Session: 6dba8ad8-2a85-4cda-a057-cf91b58a158e
babakks
left a comment
There was a problem hiding this comment.
I like the idea of bite-sized changes! Also, I'd love to see how the memory file evolve over time.
Co-authored-by: Copilot <[email protected]> Copilot-Session: 6dba8ad8-2a85-4cda-a057-cf91b58a158e
Co-authored-by: Copilot <[email protected]> Copilot-Session: 6dba8ad8-2a85-4cda-a057-cf91b58a158e
Co-authored-by: Copilot <[email protected]> Copilot-Session: 6dba8ad8-2a85-4cda-a057-cf91b58a158e
Without --max-issues-per-linter=0 --max-same-issues=0 the output is truncated to 3 findings of the same text, so fixing them reveals three previously hidden ones and a correct fix reads as a failed one. Co-authored-by: Copilot <[email protected]> Copilot-Session: 6dba8ad8-2a85-4cda-a057-cf91b58a158e
There was a problem hiding this comment.
Review details
Suppressed comments (2)
.github/skills/tech-debt-burndown/SKILL.md:184
- 🛑 Requirement: An exclusion cannot ratchet a clean package
This repository uses linters.default: none, and these linters are absent from the enable list. A rule under linters.exclusions suppresses matching findings, so adding one for a cleaned package either does nothing while the linter is disabled or hides future regressions once it is enabled - the opposite of holding the package clean. Use an enable/inclusion strategy that actually checks cleaned paths, and remove the corresponding carve-out at lines 257-258.
When a package goes clean, you may add a scoped exclusion to `.golangci.yml` that
holds it clean, in the same pull request. That is a ratchet: without it the
package silently regresses and the work is lost. Adding an exclusion is the only
edit to that file you may make. Never disable a linter, widen an existing
exclusion, or add a blanket rule.
.github/skills/tech-debt-burndown/SKILL.md:358
- 💭 Commentary: Restore the checkout on no-PR exits
After the switch at line 103, this path stops without restoring the original branch; lines 106-109 only instruct restoration after a pull request is opened. A manual run that exhausts all attempts therefore leaves the operator on a throwaway branch. Capture the original branch before switching and restore it on every exit that does not open a pull request.
If all three fail, stop. Do not open a pull request, do not open an issue, do not
comment anywhere. The run is simply silent, and the absence of a pull request is
the signal. Anything noisier turns a bad hour into a notification storm.
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Balanced
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [cli/cli](https://github.com/cli/cli) | minor | `v2.97.0` → `v2.98.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>cli/cli (cli/cli)</summary> ### [`v2.98.0`](https://github.com/cli/cli/releases/tag/v2.98.0): GitHub CLI 2.98.0 [Compare Source](cli/cli@v2.97.0...v2.98.0) #### Security A security vulnerability has been identified, and fixed, that binds the local forwarded port to all available network interfaces by default. Users of `gh codespace ports forward` are advised to update `gh` to version `v2.98.0` as soon as possible. For more information see: <GHSA-vfhh-p7hm-pxfh> #### Support worktrees in `pr checkout` Users can now checkout a pull request into a git worktree by using the new `--worktree PATH` flag in `gh pr checkout`: ```shell gh pr checkout 12 --worktree ../wt-feature ``` #### Add semantic search to `search issues` The `gh search issues` command now supports semantic search for issues. Users can select the search type by passing the `--search-type` flag: ```shell gh search issues --search-type semantic ... gh search issues --search-type hybrid ... ``` For more information about semantic search see: ["Improved Search for github issues is now generally available"](https://github.blog/changelog/2026-04-02-improved-search-for-github-issues-is-now-generally-available/). #### What's Changed ##### ✨ Features - Add --worktree flag to gh pr checkout by [@​tidy-dev](https://github.com/tidy-dev) in [#​13946](cli/cli#13946) - Set GH\_EXTENSION=1 when gh invokes an extension by [@​williammartin](https://github.com/williammartin) in [#​14072](cli/cli#14072) - Add --search-type flag for semantic and hybrid issue search by [@​michaeljacholke](https://github.com/michaeljacholke) in [#​14006](cli/cli#14006) ##### 🐛 Fixes - Fix `RESTWithNext` error type, repairing `gh status` and attestation retries by [@​williammartin](https://github.com/williammartin) in [#​13988](cli/cli#13988) - Trim spaces when parsing X-Oauth-Scopes in `gh release create` by [@​williammartin](https://github.com/williammartin) in [#​14065](cli/cli#14065) - Fix project item-add output for non-TTY by [@​zwick](https://github.com/zwick) in [#​14056](cli/cli#14056) ##### 📚 Docs & Chores - Slim down dependabot triage comments by [@​williammartin](https://github.com/williammartin) in [#​14019](cli/cli#14019) - Require explicit MR review ownership by [@​williammartin](https://github.com/williammartin) in [#​14028](cli/cli#14028) - Collapse spam triage into the agentic issue-triage workflow by [@​williammartin](https://github.com/williammartin) in [#​14027](cli/cli#14027) - Run Dependabot triage every hour by [@​sergiou87](https://github.com/sergiou87) in [#​14030](cli/cli#14030) - Route deploy key requests through api.Client by [@​williammartin](https://github.com/williammartin) in [#​13989](cli/cli#13989) - Route ssh key requests through api.Client by [@​williammartin](https://github.com/williammartin) in [#​13994](cli/cli#13994) - Route gpg key requests through api.Client by [@​williammartin](https://github.com/williammartin) in [#​13997](cli/cli#13997) - Route autolink requests through api.Client by [@​williammartin](https://github.com/williammartin) in [#​14013](cli/cli#14013) - Route extension requests through api.Client by [@​williammartin](https://github.com/williammartin) in [#​14059](cli/cli#14059) - Route release creation through api.Client by [@​williammartin](https://github.com/williammartin) in [#​14062](cli/cli#14062) - Tell agents to use the MR template in AGENTS.md by [@​williammartin](https://github.com/williammartin) in [#​14074](cli/cli#14074) - Make Dependabot triage cheaper and more decisive by [@​williammartin](https://github.com/williammartin) in [#​14079](cli/cli#14079) - Route release deletions through api.Client by [@​williammartin](https://github.com/williammartin) in [#​14077](cli/cli#14077) - Give Dependabot triage a real reachability check by [@​williammartin](https://github.com/williammartin) in [#​14087](cli/cli#14087) - Restore automatic spam issue closure by [@​williammartin](https://github.com/williammartin) in [#​14088](cli/cli#14088) - Add a scheduled tech debt burndown skill by [@​williammartin](https://github.com/williammartin) in [#​14095](cli/cli#14095) - Use reflect.Pointer instead of deprecated reflect.Ptr by [@​williammartin](https://github.com/williammartin) in [#​14098](cli/cli#14098) - Clarify what belongs in the MR template's testing section by [@​williammartin](https://github.com/williammartin) in [#​14103](cli/cli#14103) - Rename cli-code-reviewer skill to code-review by [@​BagToad](https://github.com/BagToad) in [#​14116](cli/cli#14116) - Add aw-actions group to dependabot configuration by [@​babakks](https://github.com/babakks) in [#​14123](cli/cli#14123) - Isolate tests from local machine's auth and git configuration by [@​BagToad](https://github.com/BagToad) in [#​14128](cli/cli#14128) - Don't ask for feature detection cleanup comments when not needed by [@​babakks](https://github.com/babakks) in [#​14139](cli/cli#14139) - Accept pre-release tags in deployment validation by [@​BagToad](https://github.com/BagToad) in [#​14193](cli/cli#14193) - ci: add temporary step to verify Linux repo signing keys by [@​babakks](https://github.com/babakks) in [#​14202](cli/cli#14202) - Revert "ci: add temporary step to verify Linux repo signing keys" by [@​babakks](https://github.com/babakks) in [#​14203](cli/cli#14203) - Fix issue triage action compatibility \[skip changelog] by [@​tidy-dev](https://github.com/tidy-dev) in [#​14207](cli/cli#14207) #####Dependencies - chore(deps): bump github.com/sigstore/sigstore-go from 1.2.2 to 1.3.0 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14047](cli/cli#14047) - chore(deps): bump the codeql-actions group across 1 directory with 3 updates by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14049](cli/cli#14049) - chore(deps): bump google.golang.org/grpc from 1.82.1 to 1.83.0 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14048](cli/cli#14048) - chore(deps): bump github.com/google/go-containerregistry from 0.21.7 to 0.21.8 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14066](cli/cli#14066) - chore(deps): bump actions/attest from 4.2.1 to 4.2.2 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14100](cli/cli#14100) - chore(deps): bump azure/login from 3.0.0 to 3.0.1 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14101](cli/cli#14101) - chore(deps): bump the codeql-actions group across 1 directory with 3 updates by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14091](cli/cli#14091) - chore(deps): bump github/gh-aw-actions/setup-cli from 0.83.4 to 0.85.4 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14068](cli/cli#14068) - chore(deps): bump github.com/google/go-containerregistry from 0.21.8 to 0.21.9 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14119](cli/cli#14119) - chore(deps): bump github.com/klauspost/compress from 1.19.1 to 1.19.2 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14120](cli/cli#14120) - chore(deps): bump the aw-actions group with 2 updates by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14147](cli/cli#14147) - chore: sign APT repository with both keys by [@​babakks](https://github.com/babakks) in [#​13271](cli/cli#13271) - chore(deps): bump github.com/yuin/goldmark from 1.8.4 to 1.8.5 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14029](cli/cli#14029) - chore(deps): bump actions/attest from 4.2.0 to 4.2.1 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14050](cli/cli#14050) - Bump golangci-lint in CI to v2.12.2 by [@​williammartin](https://github.com/williammartin) in [#​14102](cli/cli#14102) - chore(deps): bump the aw-actions group with 2 updates by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14124](cli/cli#14124) - chore(deps): bump google.golang.org/protobuf from 1.36.11 to 1.36.12 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14140](cli/cli#14140) - Upgrade gh-aw workflows to v0.85.4 by [@​tidy-dev](https://github.com/tidy-dev) in [#​14141](cli/cli#14141) - Bump Go to 1.26.6 by [@​github-actions](https://github.com/github-actions)\[bot] in [#​14143](cli/cli#14143) - chore: bump go to 1.26.7 by [@​babakks](https://github.com/babakks) in [#​14205](cli/cli#14205) - chore(deps): bump github.com/stretchr/testify from 1.11.1 to 1.12.1 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14204](cli/cli#14204) - chore(deps): bump the codeql-actions group across 1 directory with 3 updates by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14169](cli/cli#14169) - chore(deps): bump golang.org/x/crypto from 0.54.0 to 0.55.0 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14164](cli/cli#14164) - chore(deps): bump charm.land/lipgloss/v2 from 2.0.5 to 2.0.6 by [@​dependabot](https://github.com/dependabot)\[bot] in [#​14166](cli/cli#14166) - Bump gh-aw-actions to v0.87.1 and recompile agentic workflows by [@​BagToad](https://github.com/BagToad) in [#​14210](cli/cli#14210) #### New Contributors - [@​sergiou87](https://github.com/sergiou87) made their first contribution in [#​14030](cli/cli#14030) - [@​michaeljacholke](https://github.com/michaeljacholke) made their first contribution in [#​14006](cli/cli#14006) **Full Changelog**: <cli/cli@v2.97.0...v2.98.0> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODguMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWlub3IiXX0=-->
…180) **Symptom.** The operator's review body reached the BDFL only if that specific wake survived the debounce and the agent followed `TRIGGER_URL`. No sweep read it. **Evidence.** The sweep read `issues/comments` (conversation) and `pulls/comments` (inline). A review body is in neither; it lives in `pulls/{n}/reviews`, which nothing in this repository fetched (#147). This very PR's wake was a `pull_request_review` with an empty body, and running the new tool over this run's own window recovers it, plus a second operator review on #142 from 13:48 that no digest ever mentions. ## What changed `.github/scripts/operator-sweep` prints every operator input since the previous successful run as one chronological timeline: | channel | source | |---|---| | conversation comments | `issues/comments?since=` | | inline review comments | `pulls/comments?since=` | | **review bodies** | `pulls/{n}/reviews` over the PRs touched since the anchor | | threads opened | `issues?since=`, authored by the operator | | state changes | `issues/events`: reopened, closed, labeled | | wakes that died queued | cancelled `agent-bdfl` runs | Steps 1 and 2 of the run economy become step 1, one command. The displaced-run sweep is **deleted, not corrected**: everything it recovered lossily by display title is now recovered as content, on any wake, displaced or not. ## The premise, verified rather than assumed The review channel needs a candidate list because GitHub exposes no repo-wide reviews endpoint. Candidates are the PRs whose `updated_at` is at or after the anchor. That is complete only if submitting a review bumps `updated_at`, which is the kind of claim this sweep exists to stop believing: `cli/cli#14095` closed `2026-08-07T13:07:02Z` with zero conversation comments and no inline comment after `12:53:18Z`. It took a `COMMENTED` review at `2026-08-19T23:11:40Z`. Its `updated_at` reads `2026-08-19T23:11:41Z`. Nothing else touched it in those twelve days. ## Noise, cut mechanically rather than by training the reader to skim - Labels applied while opening a thread are dropped; the `opened` entry already carries them. - The `closed` that rides a `merged` is dropped; a merge is an outcome, not input. - What cannot be cut is documented in place: `GH_ADMIN_TOKEN` writes are operator-attributed (#50), so a BDFL merge made with it reads here as operator action. ## Liveness (#147 item 4) The last line always prints per-channel counts, the operator login swept for, and the anchor. A sweep that returns nothing forever is now distinguishable from a quiet week, and a wrong login is visible on the run it is wrong. Every walk GitHub gives no `since` for reports `INCOMPLETE:` rather than truncating silently. ## Scope note for the reviewer This edits `.github/workflows/agent-bdfl.yml`, so it is mine to merge (GOVERNANCE, workflow-file carve-out). It does not touch `agent-review.yml`, so your review applies normally. The prompt diff is the two sweeps collapsing into one step plus renumbering; the substance is in the script. Run it on any window: `.github/scripts/operator-sweep --since 2026-08-23T13:00:00Z`. --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Oliver Jan Krylow <[email protected]>
N/A - no related issue.
Description
.golangci.ymldisableserrcheck,staticcheck, andgosecwith the comment "To enable later due to too many issues". The backlog behind that comment is roughly 1900 findings. Nobody is going to clear it in one sitting, and a sweeping cleanup PR would be unreviewable, so it just sits there.This adds a
tech-debt-burndownskill designed to run unattended on a schedule. Each run picks one target, fixes it, validates it, and opens a ready-to-review pull request. The binding constraint is that the resulting PR must be reviewable in about two minutes - throughput is explicitly not the goal, because the bottleneck is human review attention, not an agent's ability to generate diffs.Three properties do most of the work:
tech-debt/*PR is already open. This is the backpressure: the loop cannot outrun the reviewer, and a stalled PR halts production rather than piling more behind it..gofiles only. A run cannot edit the workflows that schedule it, this skill file,go.mod, or CODEOWNERS, because none of them are Go source. Self-modification is blocked by construction rather than by a deny-list somebody has to keep complete..experiments/tech-debt-burndown/memory.mdis the steering channel. It has a human-ownedCurrent focussection that runs read and must never edit, plus agent-appendable sections for rejected targets and failed attempts, under a 150-line budget covering the entries only, so the fixed instructions at the top cannot crowd out learning.The first run of the skill is included so reviewers can judge the output rather than only the instructions: it takes
pkg/cmd/alias/importsfrom 5staticcheckfindings to zero.How did you test this change?
The sensor command, before the change:
And after:
gh alias importoutput is unchanged, and the existing tests are what prove it.import_test.goasserts exactwantStderrstrings covering all five rewritten messages - the two "Could not import alias" refusals, the expansion refusal, "Changed alias", and "Added alias". They pass untouched:go test ./...andmake lintwere also run. Both report failures, and stashing the change and re-running confirms both are pre-existing on a clean tree, unrelated to this PR: 3govetissues in toolchain source, and thegitpackage tests failing due to a localsafe.bareRepositorygit config.That experience is why the skill now computes a validation baseline on clean
trunkat the start of each run and compares failure sets afterwards, instead of demanding a green build. An earlier revision of this branch recorded those two specific failures in the memory file, which was a mistake: they are properties of one machine's git config and toolchain, so a run in Actions would have been misled by them. Computing the baseline per run makes the skill portable across environments.Key points
WriteString(fmt.Sprintf(x))toFprintf(&msg, x)rewrite, and the existing exact-output assertions fail if a single byte moves. The skill now spells out this exception and requires the PR to name the covering test so the claim can be checked rather than trusted.--max-issues-per-linter=0 --max-same-issues=0, and that is load-bearing rather than tidiness.golangci-linttruncates to 50 per linter and 3 of the same text by default, which inverts the oracle: you fix the three findings you were shown, re-run, and the tool displays three that were previously hidden, so a correct fix reads as a failed one and gets reverted.pkg/cmd/auth/statusreports 3 findings without the flags and 16 with them. Caught in review by @babakks.--no-configon the sensor command is deliberate but sharp: it bypasses this repo'sgosecexclusions and test-file rules, so agosecrun under it reports findings.golangci.ymlalready excludes on purpose. The skill says to cross-check.staticcheckreports noSAfindings at all - it is entirely style and quickfix categories. Useful for calibrating how much of this backlog is correctness risk versus cosmetics, and recorded in the memory file..experiments/is a new top-level directory, chosen to mark the whole thing as provisional and to give each experimental skill its own space.Notes for reviewers
Start with
pkg/cmd/alias/imports/import.go. It is 15 lines added, 25 removed, and it shows what a run actually produces. If that diff is not obviously correct at a glance, the skill has failed at its one job and the rest is not worth reading.Then read
SKILL.md, particularly "Assume nobody is watching", "Check the preconditions", "What you may change", and "Three attempts". Those four sections carry the unattended design; the rest is the target menu and mechanics.What I would most like scrutiny on:
.go-only allow-list the right boundary? It blocks self-modification cleanly, but it also means the skill can never fix debt in workflows, Makefiles, or docs.Current focussection are attempts to bound that, but they are conventions rather than mechanisms.Authorship and follow-up
Who wrote this:
Who answers review comments: