Check issue - from bug report to shipped fix
Issues: $ARGUMENTS
Open issues right now: !gh -R patrickjaja/claude-desktop-extra issue list --state open --limit 10 2>/dev/null || echo "(gh not ready)"
Latest release: !gh -R patrickjaja/claude-desktop-extra release list --limit 1 2>/dev/null || echo "(gh not ready)"
Upstream version patches target: !cat .upstream-version 2>/dev/null
The main thread orchestrates. Investigation, implementation, review and live tests run in dispatched workers. Follow the global subagent tier rules: name the tier and the reason in every dispatch.
Gates. Ask before each of these, and treat each approval as covering only its own step:
- the spec
- building or installing (the maintainer does this themselves)
- commit and push
- the release
- every public issue comment
Phase 1 - read everything
for n in <numbers>; do gh issue view $n --json title,author,createdAt,body; gh issue view $n --comments; done
--comments shows only the comments, never the body. Always fetch the body separately with --json body.
- Pull any
--diagnose gist the reporter linked with gh gist view <id> --raw > <scratch>/gist<n>/diagnose.txt. Then grep it for session type, compositor, scale, bridges and versions.
- Compare the reporter's version with
.upstream-version and the installed package (pacman -Q claude-desktop-extra). If the extract in tmp/ matches the upstream version the reports are against, it can be reused read-only.
Phase 2 - investigate in parallel (read-only)
Dispatch one worker per issue. A contained parsing or logic bug goes to sonnet. Coordinate math, renderer lifecycle, or minified-anchor work goes to opus.
Each brief carries:
- the issue text and the suspected file:line
- "READ-ONLY, no edits, no commits"
- a scratch dir per issue
- this question: ours or upstream?
Ours or upstream:
- Computer Use (
js/cu_*, the bridges) is always ours, because upstream ships no Linux CU.
- For anything else:
- Find upstream's code in the pristine bundle (
grep -a across tmp/app.asar.contents/.vite/build/index*.js).
- Confirm that our patches do not touch that path.
- Search
anthropics/claude-code issues for the same symptom.
- An upstream bug that only breaks on Linux is still ours to patch. It counts as a functional gap, not a cosmetic one.
Reproduce without the user's running app. The user's Claude is in use: never signal its processes, and identify yours by your own --user-data-dir.
- Renderer, main-process or Electron behaviour: run on a private Xvfb display with
env -i and a throwaway HOME, XDG_RUNTIME_DIR, CLAUDE_CONFIG_DIR and --user-data-dir. Use the installed binary or a scratch patched tree. A hung renderer is simulated with SIGSTOP on the main renderer.
- Wayland, wlroots or fractional scale: use headless sway per
/test-native, and run the bridge CLI directly against it. The Wayland socket path must stay under 108 bytes, so use a short mktemp -d /tmp/xx.XXXX runtime dir rather than the scratchpad.
- VMs: allowed, but never start Cowork inside a VM.
Fallback paths. When a backend has a fallback path, check which path the reporter's data came from before blaming the primary one. In #298 the bridge was correct and the Electron fallback was not.
Phase 3 - spec, with the maintainer
Load agent-skills:spec-driven-development. Write .agent/SPEC-<slug>.md. The .agent/ dir is gitignored, so never commit it or mention it in tracked docs. The spec contains:
- Capability map: one module per issue, plus its dependencies.
- Ours vs upstream table: one row per issue, with the evidence.
- Per-module design.
- A "Linux compatibility" section. Check it against every row of the
/linux matrix. No hard dependency on one distro's or one desktop's tools: optional tools are probed with _hasCmd, and the common path needs none of them. Name the Debian, AppImage and NixOS gaps explicitly.
- Commands, testing strategy, boundaries and success criteria.
- A "Rulings during implementation" section, appended as decisions are made.
Ask the real design forks with AskUserQuestion: one question per fork, with the recommended option first. Approval of the spec covers "break into tasks and dispatch". Save the plan and the ledger to .agent/tasks/plan-<slug>.md and .agent/tasks/todo-<slug>.md.
Phase 4 - implement in parallel
File ownership:
- Two tasks that edit the same file each get their own worktree, or else run in sequence. A worktree task leaves its diff uncommitted. The orchestrator then runs
git apply --check and git apply on the main tree (-3 fails on a dirty index) and copies over any untracked files.
- Implementers never touch
CHANGELOG.md, docs/, README.md, or EXPECTED_TEST_HARNESSES in scripts/run-feature-tests.sh. The integration task owns those, so parallel edits cannot conflict. New harnesses run with plain node <file> until the count is bumped.
Rules every brief must state:
- No
build-*.sh, no package build, no install, no commit.
make does not track js/. After a JS edit, touch the embedding .nim file before make.
- Patch work follows AGENTS.md strictness: every sub-patch must apply, idempotency checks positive end state,
[\w$]+ anchors.
- A new harness must fail against the old code. Ask for the before/after count.
- Live checks run on scratch trees only.
Reviews:
- Every task gets an independent per-task review.
- Feed the findings back to the same implementer through SendMessage, so it keeps its context.
- Do not re-review small verified fixes.
Phase 5 - integrate and gate
The integration worker (sonnet):
- bumps
EXPECTED_TEST_HARNESSES by the number of new harness files
- updates
docs/patches.md rows and feature docs only where they are now wrong
- writes one
## <today> CHANGELOG section, as solutions not history, without blaming our patches and without naming contributors in prose
- runs the full local gate:
touch patches/linux/<embedding>.nim && (cd patches && make -j"$(nproc)")
python3 scripts/check-upstream-absorbed.py tmp/app.asar.contents tmp/extract/usr/lib/claude-desktop/resources/ion-dist
bash scripts/check-patch-sibling-noop.sh
bash scripts/run-feature-tests.sh linux # plus core and community
python3 scripts/apply_patches.py patches <scratch copy of tmp/app.asar.contents>
# then: node --check on every patched chunk
Final review. One review covers the whole change across all repos. Run it on fable when 2+ of these hold, and on opus otherwise:
- the change carries risk-bearing semantics
- tasks shared files
- a ruling changed a design decision
- the change is over 500 non-test lines
Weigh each finding against the live evidence. A reviewer's mechanism claim can be contradicted by a measured run, as finding 1 of the #299 review was. Record accepted findings as rulings in the spec.
Phase 6 - ship
- Bridge repos first. That covers
patrickjaja/{wlroots,x11,gnome,kwin-portal}-bridge (local checkouts usually sit next to this repo, e.g. ../computer-use/<bridge>). CI and local builds fetch bridge HEAD from GitHub, so push the bridges before the main repo.
- Main repo. Commit to
master per /commit-fast. A push rejected because CI committed README versions or the Nix hash is safe to git rebase origin/master and push again; check that the CI commits touch other files first. Reference the issue numbers without closing keywords, so the issues close with the release comment instead.
- Maintainer builds and installs. Give them the commands and wait.
- Live check on the installed package:
- Run the Xvfb repro against
/usr/lib/claude-desktop/claude in a worker.
- Have the user exercise Computer Use fixes in a Code tab session. A plain chat never gets
computer_* tools on Linux, and its "computer use is off" answer is misleading.
- Read
~/.config/Claude/logs/claude-patches.log afterwards to confirm the exact argv or path.
- Release:
gh workflow run build-and-release.yml --ref master -f force_rebuild=true (or /deploy force). Watch the run in the background with gh run watch <id> --exit-status, then confirm the tag with gh release view v<ver>-<n>.
Phase 7 - close the loop with the reporter
Use the /comment voice for every comment:
- one per issue
- name the fix and the release in bold
- no blame on our patches
- no internals and no speculation
Asking for more data. Give one action plus one copy-paste command, and end with "empty output is fine too" when that is true. Never ask them to look for strings themselves. Never explain what each line would mean. claude-desktop --diagnose is the default collector. Use a targeted grep only when the evidence is not in --diagnose, which does not include claude-patches.log.
After posting:
- Close the fixed issues with
--reason completed.
- Keep an issue open only if its comment waits on reporter data.
- Answer follow-up questions from reporters in one or two lines.
Phase 8 - self-improve (always, last step)
Before reporting done, update this file: .claude/skills/check-issue/SKILL.md.
- List what went wrong or cost time in this run. That includes a wrong assumption, a confusing comment, a rejected push, a tool trap, a maintainer correction, or a gate that fired late.
- Fold, don't append. If a lesson changes how a phase should be done, edit that phase's text. Only lessons with no natural home go under "Lessons" below, as one dated line each.
- Prune. Delete a Lessons line once its rule lives in a phase. Keep this file under ~200 lines, and merge or drop the weakest lines rather than growing it.
- Never write session-specific values into the phases (minified names, pids, versions). Those go stale on the next upstream release.
- If a lesson belongs to another skill (
/comment, /test-native, /linux, /deploy), edit that skill instead and leave a pointer only if it is needed.
- Tell the maintainer in one line what changed in the skill. The edit ships with the next
/commit-fast like any other tracked file.
Lessons
- 2026-10-26 (#297-#299): an upstream reload that "should" run after a watchdog crash request was lost in live runs. Prefer guaranteed recovery over avoiding a duplicate reload, and measure before accepting a reviewer's claim about the mechanism.
- 2026-10-26: when a renderer is resumed after upstream's crash request, it executes the queued crash. Do not make "a resumed renderer must survive" an acceptance criterion.