# Issue Repro

> Reproduce a SkiaSharp issue systematically and capture structured reproduction results. Handles bugs (verify reported behavior) and enhancements (confirm feature is missing). Produces schema-validated JSON with step-by-step commands, outputs, environment details, and conclusion. Triggers: "repro #123", "reproduce #123", "reproduce issue", "try to reproduce", "can you reproduce", "repro this bug", "create reproduction".

- Skill: `mono/issue-repro` (Agent Skill, multi-file: 16 files)
- Install (CLI): `npx skillmds@latest add mono/issue-repro`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mono/issue-repro/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: mono (https://skillmd.com/u/mono)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mono/issue-repro

---


# Issue Reproduction

**Issue pipeline: Step 2 of 3 (Repro).** See [`documentation/dev/issue-pipeline.md`](../../../documentation/dev/issue-pipeline.md).

Systematically reproduce a SkiaSharp issue and produce structured, schema-validated reproduction JSON.

## ⛔ MANDATORY FIRST STEPS (do not skip)

1. Read THIS entire SKILL.md before any investigation
2. Read [references/schema-cheatsheet.md](references/schema-cheatsheet.md) for required fields and enums
3. Read [references/anti-patterns.md](references/anti-patterns.md) for critical rules

These 3 reads are REQUIRED. Do not proceed to Phase 1 until all three are loaded.

> **Quick flow:**
> 1. Load issue + any prior triage JSON
> 2. Read references: [schema-cheatsheet](references/schema-cheatsheet.md), [anti-patterns](references/anti-patterns.md)
> 3. Create brief plan (5-10 lines: strategy, platform, expected outcome)
> 4. Check environment: `docker info`, `dotnet --version`, available simulators
> 5. Build repro project and attempt reproduction
> 6. Test multiple SkiaSharp versions (3A reporter's → 3B latest → 3C main)
> 7. Generate JSON → validate → persist

```
Phase 1 (Fetch) → Phase 2 (Assess) → Phase 3 (Reproduce) → Phase 4 (JSON + Output) → Phase 5 (Validate) → Phase 6 (Persist & Present)
```

---

## Phase 1 — Fetch Issue

1. **Read the issue** (preferred):
   ```bash
   CACHE=".data-cache/repos/mono-SkiaSharp"
   [ -d ".data-cache" ] || git worktree add .data-cache docs-data-cache
   git -C .data-cache pull --rebase origin docs-data-cache
   cat $CACHE/github/items/{number}.json
   ```
   **Fallback:** `gh issue view {number} --repo mono/SkiaSharp --json title,body,labels,comments,state,createdAt,closedAt,author`
2. **Triage boost** — if `$CACHE/ai-triage/{number}.json` exists, extract `classification.platforms[]`, `evidence.bugSignals`, `analysis.nextQuestions[]`, and `output.actionability.suggestedReproPlatform` as **hints** (verify independently). The `suggestedReproPlatform` (`linux`|`macos`|`windows`) indicates which CI runner was selected for reproduction.

---

## Phase 2 — Assess & Plan

### 1. Classify

Read [references/bug-categories.md](references/bug-categories.md) to classify the issue type and determine the reproduction strategy. That file covers bugs (Sections 1–6), enhancements (Section 7), platform parity gaps (Section 8), and documentation issues (Section 9) — including which conclusion values and `layer` to use for each.

### 2. Extract reporter's version & TFM

- `{reporter_version}`: exact SkiaSharp NuGet version
- `{reporter_tfm}`: target framework (e.g., `net10.0`)
- `{reporter_code}`: reproduction code from the issue
- Reporter's platform/OS

If not stated, use the latest stable release. **.NET is forward-compatible** — `net8.0` libraries work on `net10.0` apps. Never say "doesn't support .NET X" for newer TFMs.

### 3. Environment check

**⚠️ Run `dotnet --info` in `/tmp/skiasharp/repro/{timestamp}/{number}/`** (NOT the SkiaSharp repo, which has `global.json` pinning SDK 8.0). Record SDK version, workload versions, and runtime version. `{timestamp}` is the current UTC time in `yyyyMMdd-HHmmss` format.

Also check: Docker (`docker --version`), Playwright MCP tools, GPU availability, .NET workloads (`dotnet workload list`). Install missing workloads now — don't wait.

### 4. Determine reproduction platform

| Priority | Signals | Platform file |
|----------|---------|---------------|
| 1 | Blazor, WASM, WebAssembly, SKHtmlCanvas, browser error | [platform-wasm-blazor.md](references/platform-wasm-blazor.md) |
| 2 | WPF, WinForms, WinUI, UWP | [platform-windows-desktop.md](references/platform-windows-desktop.md) |
| 3 | iOS, Android, MAUI, Xamarin | [platform-mobile.md](references/platform-mobile.md) |
| 4 | Linux, Docker, container, NativeAssets.Linux | [platform-docker-linux.md](references/platform-docker-linux.md) |
| 5 | (none) | [platform-console.md](references/platform-console.md) |

All platform files fall back to `platform-console.md` for core SkiaSharp bugs.

**Tie-breaking:** If multiple platform signals (e.g., "WASM + WPF"), use the highest priority. If reporter says "works on X, fails on Y", reproduce on Y first, test X in Phase 3D.

**Read the selected platform file.** Follow its Create → Build → Run → Verify steps, substituting `{reporter_version}`, `{reporter_tfm}`, and `{reporter_code}`.

### 5. Plan

Output a brief plan before executing (5-10 lines: what platform, what version, what approach).

---

## Key Rules (read before Phase 3)

Read [references/anti-patterns.md](references/anti-patterns.md) for the full list. Critical rules:

1. **Source code investigation.** Stop at "did it reproduce." Root cause is the `issue-fix` skill's job.
2. **Editorial judgment in conclusion.** If the reported behavior occurred, it's `reproduced` — even if by-design.
3. **Stopping at build success.** Many bugs manifest at RUNTIME. Build ≠ runtime.
4. **Stale build artifacts.** Fresh project dirs or `rm -rf bin/ obj/` between versions.
5. **Honesty over completion.** `not-reproduced` and `needs-platform` are VALID SUCCESS conclusions. Reporting inability to reproduce is correct behavior, NOT failure. NEVER invent output you did not observe from an actual command execution.
6. **NEVER modify product source.** Do not edit files in `binding/`, `externals/`, `samples/`, `source/`, `tests/`, `utils/`, or any other product source during reproduction. Repro creates NEW test projects in `/tmp/skiasharp/repro/{timestamp}/` only (`{timestamp}` is the current UTC time in `yyyyMMdd-HHmmss` format). If you find yourself editing SkiaSharp source, you have crossed into fix territory — stop.
7. **NEVER use `store_memory`.** Reproduction produces JSON artifacts, not memories. Storing unverified observations as permanent facts pollutes all future sessions.
8. **NEVER skip validation.** You MUST run `validate-repro.ps1` (or `.py` fallback) and see ✅ before persisting. Mentally checking fields is not validation. If the script isn't run, the reproduction is invalid.

**Intermittent bugs:** If results are inconsistent, run 3–5 times. Reproduced ≥1 time → `reproduced` with note "Intermittent: X/Y runs". Never reproduced after 5 → `not-reproduced`.

**Effort budget:** Phases 1–2: ~5 min. Phase 3A: ~15–20 min. Phases 3B–3D: ~10–15 min. Total: ~30–50 min. If stuck after 3+ substantially different approaches, conclude with what you have.

---

> **Pre-flight — confirm before reproducing:**
>
> - [ ] Issue data loaded (cache JSON or GitHub API)
> - [ ] Prior triage JSON loaded (if exists in `ai-triage/`)
> - [ ] Read [references/schema-cheatsheet.md](references/schema-cheatsheet.md) for required fields and enums
> - [ ] Read [references/anti-patterns.md](references/anti-patterns.md) — at least the critical rules
> - [ ] Environment checked: `docker info` (if needed), `dotnet --version`, `xcodebuild -showsdks` (if iOS)
> - [ ] Created a brief plan (5-10 lines: reproduction strategy, which platform, what you expect)
> - [ ] Never use `sudo` — if a command requires it, find an alternative approach

## Phase 3 — Reproduce

> **Overview — you will test up to 4 configurations:**
> - **3A:** Reporter's version on primary platform *(always)*
> - **3B:** Latest stable release *(always)*
> - **3C:** Main branch source *(MANDATORY if 3B still reproduced)*
> - **3D:** Cross-platform verification *(conditional — see table below)*
>
> **🛑 MINIMUM 2 VERSIONS REQUIRED.** You must test at least the reporter's version (3A) AND latest stable (3B). Single-version reproductions are incomplete and will fail schema validation. This applies to ALL conclusion types — bugs (`reproduced`/`not-reproduced`) AND enhancements (`confirmed`/`not-confirmed`). For enhancements: a feature may exist in one version but not another, or may have been removed. Version testing reveals this.

### 3A. Reproduce with reporter's version

Follow the platform file from Phase 2.4. For each step, capture:

| Field | Limit |
|-------|-------|
| `command` | Exact command (redact paths) |
| `exitCode` | 0=success, non-zero=failure |
| `output` | 2KB success, 4KB failure |
| `filesCreated` | Filename + source code content for repro files |
| `layer` | `setup` / `csharp` / `c-api` / `native` / `deployment` / `investigation` |
| `result` | `success` / `failure` / `wrong-output` / `skip` |

**Step `result` = what actually happened** (technical outcome), not whether it was expected. A build that fails is `result: "failure"` even if that confirms the bug. See [references/anti-patterns.md](references/anti-patterns.md) for details.

> **Note:** Use `layer: "investigation"` for source-code analysis steps in enhancement confirmations (grep, file reading). See Phase 2.1 for the full enhancement flow.

**Push hard.** Don't bail early. Only conclude `not-reproduced` after genuinely exhausting approaches.

### 3B. Test on latest release

> **⚠️ Clean build required:** Create a fresh project directory per version (`/tmp/skiasharp/repro/{timestamp}/{number}-latest/`) or `rm -rf bin/ obj/` before building. Never just `sed` the version — stale native binaries produce unreliable results. See [references/anti-patterns.md](references/anti-patterns.md) #7.

Use the same platform strategy from 3A with the latest stable SkiaSharp. Record in `versionResults`.

### 3C. Test on main branch (if reproduced on latest)

> **🛑 Do NOT skip when reproduced on latest.** If the bug reproduces on the latest stable release, testing main is MANDATORY — it tells us whether a fix exists but hasn't been released.

1. Bootstrap: `[ -d "output/native" ] && ls output/native/ | head -5 || dotnet cake --target=externals-download`
2. **Build & run the platform-appropriate sample** under `samples/Basic/<platform>/`. Each platform file has a "Main Source Testing (Phase 3C)" section — follow it.
3. Record result. If fixed on main but not released, note the version gap.

> **Clean up:** Revert sample file changes with `git checkout -- samples/`.

### 3D. Cross-platform verification (conditional)

| Primary result | Run 3D? |
|----------------|---------|
| `reproduced` (platform-specific) | **Yes** — test alternative platform |
| `not-reproduced` + reporter on different platform | **Yes** |
| `reproduced` (pure API bug, no platform signals) | **Skip** — note why |
| `not-reproduced` + same platform as reporter | No |

**Time cap:** 5 minutes. **Default alternative:** Docker Linux x64.

| Primary | Best alternative |
|---------|-----------------|
| Console macOS | Docker Linux x64 |
| WASM/Blazor | Console on host |
| Docker Linux | Console on host |
| Windows (reported) | Console + Docker Linux |

Test reporter's version only. Derive `scope`: reproduced on ≥2 platforms → `"universal"`, primary only → `"platform-specific/{platform}"`, skipped → `"unknown"`.

**For `confirmed`/`not-confirmed` conclusions:** Derive `scope` from your investigation — if the gap exists in ALL platform views → `"universal"`, if the gap is specific to one platform's view (e.g., Blazor only) → `"platform-specific/{platform}"`. Always set `scope` for confirmed conclusions (schema requires it).

### Simulation Strategy (last resort)

Some bugs live in framework-specific code (MAUI views, WPF handlers, Uno controls) that can't be run in a console app. Use this **escalation order** — only move to the next level if the previous one fails:

1. **Direct run** — Build and run the actual project type (MAUI, WPF, etc.)
2. **Real platform** — Use a simulator/emulator (iOS Simulator, Android Emulator)
3. **Automation** — Use Appium or Playwright to drive UI testing
4. **Docker** — Run in a container (Linux-only scenarios)
5. **Simulate** — Extract the logic into a console app and model the inputs

**When to simulate:** Only when steps 1-4 are impossible (wrong OS, no SDK, no device). Example: a WPF bug on a macOS host with no Windows VM.

**How to simulate:** Extract the suspect code path into a standalone console app. Model the framework inputs (e.g., resize events, binding updates) and verify the logic path triggers the bug.

**Recording:** Set `reproProject.type: "simulation"` in the JSON. The triage's `classifiedPlatform` vs repro's `type: "simulation"` shows the gap clearly.

**Example:** Opus reproduced SKXamlCanvas NRE (#3430) by extracting the resize handler logic into a console app, feeding 73 dimension combinations, and confirming 100% NRE rate when width/height were 0.

---

## Phase 4 — Generate JSON

### 1. Choose conclusion

Read [references/conclusion-guide.md](references/conclusion-guide.md). Key question: did the reported claim hold true?

| Conclusion | When | Issue Types |
|------------|------|-------------|
| `reproduced` | Reported behavior occurred, including wrong/incomplete output (even if by-design) | Bugs |
| `not-reproduced` | Reported behavior did not occur | Bugs |
| `confirmed` | Reporter's claim verified (feature IS missing, docs ARE wrong) | Enhancements, features, docs |
| `not-confirmed` | Reporter's claim not verified (feature exists, docs are correct) | Enhancements, features, docs |
| `needs-platform` / `needs-hardware` | Requires unavailable platform/hardware | Any |
| `partial` / `inconclusive` | Partial or ambiguous results | Any |

### 2. Generate JSON

Write to `/tmp/skiasharp/repro/{timestamp}/{number}.json`. Use the exact literal path `/tmp/skiasharp/repro/{timestamp}/` — do NOT substitute `$TMPDIR` or any other variable. `{timestamp}` is the current UTC time in `yyyyMMdd-HHmmss` format. Run `mkdir -p /tmp/skiasharp/repro/{timestamp}` first if needed.

Schema: [references/repro-schema.json](references/repro-schema.json). See [references/repro-examples.md](references/repro-examples.md) for full worked examples.

**Key rules** (schema enforces the rest):
- **Optional fields: OMIT entirely** — do NOT set to `null`
- `environment.dotnetSdkVersion`: exact SDK from `dotnet --info`. Include `wasmToolsVersion` for WASM.
- `versionResults`: include `platform` field (e.g., `"host-macos-arm64"`, `"docker-linux-x64"`)
- Redact paths (`/Users/{name}/` → `$HOME/`), tokens, credentials.

### 3. Feedback (when triage was consumed)

If reproduction contradicts triage, record in `feedback.corrections[]`:
```json
{ "source": "triage", "topic": "classification", "upstream": "...", "corrected": "..." }
```

### 4. Generate output (required for definitive conclusions)

When conclusion is `reproduced`, `not-reproduced`, `confirmed`, or `not-confirmed`, generate the `output` object with actionability, actions, and a proposed response. Skip for blocked conclusions (`needs-platform`, `needs-hardware`, `partial`, `inconclusive`).

#### Choosing suggestedAction

| Scenario | suggestedAction | Confidence |
|----------|----------------|------------|
| Reproduced on all versions including latest/main | `needs-investigation` | 0.90+ |
| Reproduced on reporter's version, fixed on latest | `close-as-fixed` | 0.85+ |
| Reproduced on reporter's version, fixed on main (unreleased) | `keep-open` | 0.80+ |
| Not reproduced — likely environment/config issue | `request-info` | 0.70+ |
| Not reproduced — works on all tested versions | `close-as-fixed` | 0.75+ |
| Wrong output confirmed | `needs-investigation` | 0.85+ |
| Reproduced but appears working-as-designed | `close-with-docs` | 0.70+ |
| Confirmed — feature/docs gap verified | `needs-investigation` | 0.85+ |
| Not confirmed — feature/docs actually exist | `close-with-docs` | 0.75+ |

#### Writing proposedResponse

See [references/response-guidelines.md](references/response-guidelines.md) for tone, evidence requirements, status thresholds (`ready`/`needs-human-edit`/`do-not-post`), and conclusion-specific templates.

#### Workarounds

If reproduction testing reveals a workaround (version upgrade, config change, workload install, API alternative), add to `output.workarounds[]` as simple strings and include in the proposed response body.

#### Missing info

When conclusion is `not-reproduced` or the response asks for more details, populate `output.missingInfo[]` with specific items needed (e.g., "Exact .NET SDK version", "Full exception stack trace", "Minimal reproduction project"). Reference these in `proposedResponse.body`.

#### Actions

Use the same action types as triage. Common repro actions:

| Action | When | Risk |
|--------|------|------|
| `update-labels` | Add platform labels confirmed by repro, remove incorrect ones | low |
| `close-issue` | Bug fixed in latest + reporter can upgrade | medium |
| `set-milestone` | Bug confirmed, target a release | low |
| `link-related` | Discovered related issue during repro | low |

---

## Phase 5 — Validate

> **🛑 PHASE GATE: You CANNOT persist without passing validation.**
> **Skipping validation = INVALID reproduction. The task is incomplete.**

### 1. Validate (MANDATORY — run first)

```bash
# Try pwsh first, fall back to python3
pwsh .agents/skills/issue-repro/scripts/validate-repro.ps1 /tmp/skiasharp/repro/{timestamp}/{number}.json \
  || python3 .agents/skills/issue-repro/scripts/validate-repro.py /tmp/skiasharp/repro/{timestamp}/{number}.json
```

- **Exit 0** = ✅ valid → proceed to Phase 6
- **Exit 1** = ❌ fix the errors listed in the output, then re-run. Repeat up to 3 times.
- **Exit 2** = fatal error, stop and report

> **⚠️ NEVER hand-roll your own validation. NEVER assume it passes. RUN THE SCRIPT.**
> **If you have not seen ✅ from the validator, DO NOT proceed to Phase 6.**

> **🛑 PHASE GATE: Validator MUST have printed ✅ before proceeding to Phase 6.**

## Phase 6 — Persist & Present

### 1. Persist (only after validator prints ✅)

Copy the validated JSON to `output/ai/` for collection.

```bash
pwsh .agents/skills/issue-repro/scripts/persist-repro.ps1 /tmp/skiasharp/repro/{timestamp}/{number}.json
```

This copies the JSON to `output/ai/` mirroring the data-cache structure.

### 2. Present summary

```
✅ Reproduction: ai-repro/{number}.json

Conclusion:  reproduced
Steps:       5 (3 success, 1 failure, 1 skip)
Environment: macOS arm64, SDK 10.0.102

Version results:
  SkiaSharp 2.88.9 (reporter): ❌ REPRODUCED
  SkiaSharp 3.116.1 (latest):  ❌ REPRODUCED
  main (source):               ✅ not-reproduced
```

