.agents/skills/issue-fix/SKILL.md
Issue pipeline: Step 3 of 3 (Fix). See documentation/dev/issue-pipeline.md.
Fix bugs in SkiaSharp with minimal, surgical changes.
🛑 PHASES MUST BE EXECUTED IN STRICT ORDER. NO PARALLELIZATION. NO REORDERING.
Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5 → Phase 6 → Phase 7 → Phase 8 → Phase 9STOP at each phase gate. Do not proceed until gate criteria are met. Phases may be abbreviated when
ai-triage/{n}.jsonand/orai-repro/{n}.jsonexist — but you must explicitly consume them and meet the gate with evidence (don’t redo the work). NEVER say "in parallel" — phases are strictly sequential. NEVER start new research (Phase 3) before PR exists (Phase 2). NEVER usestore_memory— fixes produce JSON artifacts and PRs, not memories.
1. Understand → Fetch issue, consume ai-triage/ai-repro if present
2. Create PR → 🛑 STOP: Create PR before ANY *new* research
3. Research → Delta research (triage already did first-pass)
4. Reproduce → Prefer ai-repro project; Docker only if needed
5. Investigate → Root cause (guided by repro version matrix + triage codeInvestigation)
6. Fix → Minimal change
7. Test → Regression test + existing tests
8. Finalize → Rewrite PR description, link all fixed issues
9. Fix JSON → Generate, validate, and persist ai-fix/{n}.json
docs-data-cache) for ai-triage/ai-repro handoffdocker --version)pwsh --version # Requires 7.5+
# Cache worktree
[ -d ".data-cache" ] || git worktree add .data-cache docs-data-cache
git -C .data-cache pull --rebase origin docs-data-cache
CACHE=".data-cache/repos/mono-SkiaSharp"
TRIAGE="$CACHE/ai-triage/NNNN.json"
REPRO="$CACHE/ai-repro/NNNN.json"
TRIAGE exists: treat it as the authoritative classification + codeInvestigation. Extract key details and uncertainties.REPRO exists: treat it as the authoritative factual reproduction record (versions tested + minimal repro source).If cache is missing the issue/JSONs, fall back to gh.
Extract (from issue + triage/repro if present):
ai-repro)Do not redo triage’s work here. No deep code investigation and no broad related-issue search yet.
ai-triage/NNNN.json existsai-repro/NNNN.json exists🛑 DO NOT search for related issues yet. DO NOT investigate yet. 🛑 Your ONLY next action is Phase 2: Create the Draft PR.
The PR must exist BEFORE any research or investigation begins.
🛑 THIS PHASE IS BLOCKING. Complete it before ANY other work.
Do NOT:
- Search for related issues (that's Phase 3)
- Read comments on other issues (that's Phase 3)
- Look at code (that's Phase 5)
- Try to reproduce (that's Phase 4)
Do ONLY:
- Create branch
- Push empty commit
- Create draft PR with template
- Add "copilot" label
git checkout -b dev/issue-NNNN-short-description
git commit --allow-empty -m "Investigating #NNNN: [description]"
git push -u origin dev/issue-NNNN-short-description
gh pr create --draft --title "Investigating #NNNN: [description]" --body "[template]"
gh pr edit --add-label "copilot"
Create PR using investigation template from references/pr-templates.md.
The PR description is your living document:
Update the PR description OFTEN — after every significant step.
🛑 STOP. Verify the PR URL exists before proceeding to Phase 3.
Only after confirming the PR is created should you begin research.
🛑 PREREQUISITE: Phase 2 must be complete. PR must exist.
If you have not created the draft PR yet, STOP and go back to Phase 2.
If ai-triage/NNNN.json exists, it already contains:
Your job in Phase 3 is delta research only:
🛑 CRITICAL: This phase often SOLVES the bug.
The community may have already diagnosed the root cause in issue comments. READ ALL COMMENTS on the most relevant related issues before investigating yourself.
Search GitHub issues for:
undefined symbol: uuid_generate_random)Linux ARM64)For EACH related issue found:
Update PR with all related issues and extracted information.
If a related issue already contains the root cause diagnosis, document it in the PR, but you MUST still proceed to Phase 4 (Reproduce) to validate the hypothesis.
⛔ REPRODUCTION IS MANDATORY.
This phase is satisfied either by:
- Re-running the minimal repro locally, OR
- Consuming an existing
ai-repro/NNNN.jsonthat already reproduced the issue on the relevant version/platform and includes the minimal repro source.Even if you think you know the root cause from Phase 3:
- Community diagnosis could be a workaround, not the real fix
- The hypothesis could be wrong or incomplete
- You need evidence, not assumptions
If ai-repro/NNNN.json exists and conclusion is reproduced:
reproductionSteps[].filesCreated[].content into a local folder (e.g., /tmp/skiasharp/repro/NNNN/) and run it.If no ai-repro exists:
| Attribute | Must Match |
|---|---|
| OS (macOS/Windows/Linux) | ✅ |
| Architecture (x64/ARM64) | ✅ |
| .NET version | ✅ |
| SkiaSharp version | ✅ |
For cross-platform testing, see references/docker-testing.md.
Example (adapt platform to match the issue):
# Replace with the platform from the issue
docker run --platform linux/arm64 -it <dotnet-sdk-image> bash
Add to PR description:
| Environment | Version | Result |
|---|---|---|
| [Platform from issue] | [version] | ❌ Crashes |
| [Different platform] | [version] | ✅ Works |
Try hard and exhaust all options before giving up:
Document each attempt in PR. After exhausting ALL options: ask user for details, but still proceed with code review while waiting.
ai-repro/NNNN.json as the baseline reproduction record💡 Often already done! If Phase 3 found a diagnosis in related issue comments, this phase is just confirmation. Don't re-investigate what's already known.
For detailed debugging methodology, see documentation/dev/debugging-methodology.md.
"Why does this work on [other platform/version] but fail here?"
The answer to this question IS the root cause. Focus your investigation on finding the difference.
When a bug affects one platform but not another, build both platforms locally and compare:
# Build x64 native (in Docker)
bash ./scripts/Docker/debian/amd64/build-local.sh
# Build ARM64 cross-compile (in Docker)
bash ./scripts/Docker/debian/clang-cross/build-local.sh arm64 10
# Compare DT_NEEDED entries (the linked libraries)
docker run --rm -v $(pwd):/work debian:bookworm-slim bash -c \
"apt-get update -qq && apt-get install -y -qq binutils >/dev/null && \
echo '=== x64 ===' && readelf -d /work/externals/skia/out/linux/x64/libSkiaSharp.so.* | grep NEEDED && \
echo && echo '=== ARM64 ===' && readelf -d /work/externals/skia/out/linux/arm64/libSkiaSharp.so.* | grep NEEDED"
If a library appears in one but not the other, investigate:
-lfoo for both? → Check externals/skia/out/linux/{arch}/obj/SkiaSharp.ninjanative/linux/build.cake and externals/skia/gn/skia.gnigrep -rn "MethodName" binding/SkiaSharp/
grep -r "sk_.*methodname" binding/SkiaSharp/
⚠️ CRITICAL: Don't mistake a workaround for the root cause fix.
Example from #3369:
undefined symbol: uuid_generate_random on ARM64-luuid to linker flagsHow to tell the difference:
Stop investigating when you can answer:
Principle: Minimal change. Fix only what's broken.
For guidance on specific types of fixes, see:
dotnet cake --target=externals-macos --arch=arm64 # macOS ARM64
dotnet cake --target=externals-linux --arch=arm64 # Linux ARM64 (in Docker)
dotnet cake --target=externals-download # If output/native/ empty
| Affected Class | Test File |
|---|---|
| SKCanvas | tests/Tests/SKCanvasTest.cs |
| SKBitmap | tests/Tests/SKBitmapTest.cs |
| SKImage | tests/Tests/SKImageTest.cs |
| Other | Find matching *Test.cs |
Name: Issue_NNNN_BriefDescription()
dotnet test tests/SkiaSharp.Tests.Console.sln
Tests MUST pass. Verify fix on original platform.
Rewrite PR description using final template from references/pr-templates.md.
Link ALL fixed issues (including related issues that have the same root cause):
Fixes #3369
Fixes #3272
Mark PR as ready for review (remove draft status).
Generate structured output for the pipeline. Schema: references/fix-schema.json Examples: references/fix-examples.md
Write to /tmp/skiasharp/fix/{timestamp}/{number}.json — use this exact literal path, do NOT substitute $TMPDIR or any other variable. {timestamp} is the current UTC time in yyyyMMdd-HHmmss format. Create the directory first with mkdir -p.
meta: schemaVersion "1.0", number, repo, analyzedAt (ISO 8601 UTC)inputs: { triageFile, reproFile } — paths to upstream files consumed (if any)status: { value, reason } — value is one of in-progress, fixed, cannot-fix, needs-info, duplicate. reason is a required one-sentence explanation.summary: one-paragraph description of what was fixed and how (include root cause, fix approach, and verification outcome; minLength 20)rootCause: { category, area, description, confidence?, affectedFiles? } — what was wrong and why
category: one of logic-error, memory-safety, threading, api-misuse, dependency, upstream-skia, missing-feature, otherarea: one of managed, binding, native, build, packaging, tests, docsconfidence: 0.0–1.0 (0.95+=verified, 0.80+=strong evidence, <0.80=hypothesis)changes: { files: [{ path, changeType, summary }], breakingChange, risk }
changeType: one of added, modified, removedrisk: one of low, medium, hightests: { regressionTestAdded, testsAdded?, command?, result }
result: one of passed, failed, not-runtestsAdded: [{ file, name, description? }]verification: { reproScenario, method, notes? } — did the repro scenario pass after the fix?
reproScenario: one of passed, failed, not-run, not-applicablemethod: one of automated-test, manual-repro, visual-inspection, code-review (required)blockers: string array — required when status.value is cannot-fix or needs-info. Each item is one actionable blocker.pr: { number?, url, status } — required when status.value is fixedfeedback: corrections to triage/repro findings (optional, see below)relatedIssues: other issue numbers fixed or related — required (minItems 1) when status.value is duplicateIf the fix discovered that triage or repro got something wrong, record it:
"feedback": {
"corrections": [
{
"source": "triage",
"topic": "root-cause",
"upstream": "Triage suggested native Skia bug",
"corrected": "Actually a missing managed-side validation"
}
]
}
# Try pwsh first, fall back to python3
pwsh .claude/skills/issue-fix/scripts/validate-fix.ps1 /tmp/skiasharp/fix/{timestamp}/{number}.json \
|| python3 .claude/skills/issue-fix/scripts/validate-fix.py /tmp/skiasharp/fix/{timestamp}/{number}.json
⚠️ NEVER use hand-rolled validation. Always use the scripts above.
Copy the validated JSON to output/ai/ for collection.
pwsh .claude/skills/issue-fix/scripts/persist-fix.ps1 /tmp/skiasharp/fix/{timestamp}/{number}.json
This copies the JSON to output/ai/ mirroring the data-cache structure.
| Issue | Recovery |
|---|---|
| Can't reproduce | Ask user for exact environment details |
| Fix causes test failures | Revert, re-analyze root cause |
EntryPointNotFoundException | Rebuild natives after C API changes |
| Can't test on required platform | Use Docker or ask user to verify |
| Proposed fix is a workaround | Stop — find why it works on other platforms |
| Docker build uses cached layers | Use docker build --no-cache to rebuild |
| Linker silently skips library | See debugging-methodology.md |
Before marking complete, verify ALL gates were passed:
ai-repro/NNNN.json)validate-fix.ps1/.py (saw ✅), and persistedSee the triage and repro anti-patterns references for the full lists. Critical rules for fix:
#0 (CRITICAL): NEVER use store_memory. Fixes produce JSON artifacts and PRs, not memories.
#1 (CRITICAL): NEVER skip the validation script. You MUST run validate-fix.ps1 (or .py fallback) and see ✅ before persisting. Mentally checking fields is not validation. If the script isn't run, the fix JSON is invalid.
#2 (CRITICAL): NEVER skip phases or reorder them. Sequential execution is required — see the ⛔ block at the top.