Run a "verify → diagnose → fix → re-verify" loop via local Playwright MCP (headed mode), using snapshot + console + network as evidence each round; on FAIL, fix the code and verify again in the next round. For the Hard cap and HITL trigger timing, see Step 0b (the sole authoritative source in this document; all other places only reference it without restating)。
Differences from existing skills
| Skill | Purpose | When to use |
|---|---|---|
playwright-human-in-the-loop |
Operational: confirm before each major browser operation | Execute one-off flows in an admin interface |
verify-fix-loop |
Repair: loop verification and code fixing | UI bug fixing, E2E behavior alignment |
verify-evidence-loop |
Evidence: 4-dimension literature verification of technical claims | Evidence checks before high-risk decisions |
design |
Planning: architecture decisions and plan outputs (non-execution) | When the problem exceeds the fix scope and needs re-planning, hand off |
This skill does not reinvent: bring-your-own-MCP (local Playwright headed), reuse playwright-human-in-the-loop 's checklist-driven verification mode, reuse verify-evidence-loop 's iteration + HITL gate mode. When beyond the fix scope, delegate via HITL option 4 to /design(requires Agent tool)。
Step 0: Pre-checks
0a. Playwright MCP Headed mode confirmation
Headed mode is determined by the Playwright MCP server startup parameter (--headed flag), not a per-call switch.
How to check:
- Try calling
mcp__playwright__browser_snapshot— if the tool does not exist or returns an error, treat MCP as not started - Ask the user whether the current Playwright MCP server is started in headed mode
If headed is not enabled, prompt to add in ~/.claude.json or the project's .mcp.json :
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest", "--headed"]
}
}
}
After adjusting, you mustrestart Claude Code to let MCP reconnect. Necessity of Headed mode: the user can observe the browser in sync, visually confirm during HITL, and the debug experience is far better than headless.
If MCP is unavailable → stop the flow, ask the user to start it and retry,do not attempt to substitute with Bash。
0b. Scope confirmation (mandatory HITL) — the sole source for hard cap / HITL trigger timing
Use AskUserQuestion to collect four required pieces of information (none may be missing):
About to start the verify-fix iterative loop:
- Goal: [$ARGUMENTS summary]
- Estimate: ~5-10k tokens per round,hard cap = 5 rounds(agents drift easily over multiple rounds, hence this cap)
- After 2 rounds (from Round 3) ask each round whether to continue
Please provide:
- Target URL to verify(e.g.:
http://localhost:3000/login)- PASS condition(specific, verifiable; e.g.: "after clicking Login, redirect to
/dashboardand no error in console")- Modifiable file scope(glob; e.g.:
src/components/Login/**、src/api/auth.ts) — used to limit the editing boundary of the Fix phase- Dev server status(already running / needs to be started first / not applicable, see 0c)
If any item is missing → must not enter Step 1.
PASS condition DSL (recommended format)
One criterion per line, type + parameters + expected value. Phase A performs direct mechanical comparison, avoiding the uncertainty of "free text → LLM interpretation."
| Type | Syntax | Example |
|---|---|---|
url |
url: <regex 或字串> |
url: /dashboard$ |
element |
element: <ref 或 selector> |
element: button[name="Sign out"] |
text |
text: "<字串>" |
text: "Welcome, Alice" |
not-element |
not-element: <selector> |
not-element: .login-form |
console |
console: no-error or no-warning |
console: no-error |
network |
network: no-4xx / no-5xx / no-failure |
network: no-failure |
eval |
eval: <JS 表達式(須 truthy)> |
eval: window.localStorage.getItem('token') !== null |
Example:
url: /dashboard$
element: nav [name="User menu"]
console: no-error
network: no-5xx
Free text input → automatically converted to DSL at the end of Step 0b, read back to the user for confirmation before starting.
0c. Dev Server Strategy
Based on the answer to item 4 of 0b:
| Status | Behavior |
|---|---|
| Already running | Go directly to Step 1;browser_navigate when connection is refused, fall back to 0c HITL, do not auto-start |
| Needs to be started first | AskUserQuestion Ask whether the skill should start it with Bash.Default is not to start, ask the user to open another terminal and run npm run dev then come back |
| Not applicable(static HTML / production URL) | Skip server check |
Why not auto-start by default: the dev server is a long-running process; auto-starting will consume token budget continuously tailing stdout; when the skill ends, the lingering process is hard to reclaim; connection retry / port conflict logic is complex and error-prone.
If the user insists on letting the skill start it → Bash(run_in_background=true) + health check after 5 seconds (browser_navigate whether 200). On failure, terminate the process and report. When the skill endsforce promptthe user to kill it manually.
0d. Persistent round log (automatically created)
Before starting Step 1, create the file:.claude/verify-fix-loop/<timestamp>-<slug>.md
<timestamp>:YYYYMMDD-HHMMSS<slug>: the last path segment of the URL in kebab-case, truncated to 30 characters (pure query →verify-<random>)- At the end of each round, append one
## Round Nblock (diagnosis, files changed, verdict); modified files are not automatically reverted - HITL early exit / cap-exceeded / PASS all write the final report to the same file
- Purpose: cross-session handoff (with
/handoff), PR description citation, retrospective debug trail - Not committed to git: recommended
.gitignoreadd.claude/verify-fix-loop/
Step 1: Verify-Fix Loop
Execute Phase A → B → C → D round by round until PASS or until the hard cap from Step 0b is reached; starting from round 3, pass through the HITL Gate of Step 2 before entering Phase A.
Phase A: Verify
Verify each PASS condition item by item (checklist-driven, better than free-text judgment):
browser_navigate(target_url)— go to the target pagebrowser_wait_for(...)— wait for the ready selector / text according to the PASS condition- Perform operations according to the PASS condition (click / fill / select, etc.)
browser_snapshot()— obtain the ARIA treebrowser_console_messages()— collect all console messagesbrowser_network_requests()— collect all network requests
Judge each PASS condition item by item (corresponding one-to-one with the Step 0b PASS condition DSL):
| DSL type | Verification item | Source | PASS condition |
|---|---|---|---|
url: |
URL matches expectation | snapshot.url | String or regex match |
element: |
Expected element appears | snapshot ARIA | Contains expected ref / selector |
not-element: |
Expected element does not exist | snapshot ARIA | Does not contain specified selector |
text: |
Expected text appears | snapshot ARIA(accessible name / textContent) | Contains specified string |
console: |
No console error / warning | browser_console_messages |
No level: error(or warning) |
network: |
No failed network requests | browser_network_requests |
No 4xx / 5xx / failed |
eval: |
Custom condition (DOM / state) | browser_evaluate |
Expression truthy |
All PASS → exit success;Any FAIL → enter Phase B.
Phase B: Diagnose
Extract root cause from failure evidence, output fixed format:
Round {N} Diagnosis
- Failed criteria:
- [criterion 1]
- [criterion 2]
- Evidence:
- snapshot excerpt: <關鍵節點>
- console error: <stack trace 首行>
- network failure: <method> <url> → <status>
- Suspected root cause: <一句話>
- Suspected files (within allowed_paths):
- <path 1>
- <path 2>
If suspected files exceed allowed_paths → mark [OUT-OF-SCOPE],do not edit directly, leave for HITL gate to remind user to expand scope.
Phase C: Fix
Edit according to diagnosisOnly within allowed_paths files:
- Use Read → Edit / Write, minimal change principle
- Record file diff summary in round_log before and after modification (
+N -M lines) - Target only 1 root cause at a time; do not incidentally do unrelated cleanup
Hard prohibitions:
| Prohibited behavior | Reason |
|---|---|
| Modify tests / spec to make assert looser | "Passing for the sake of passing" is not a real fix |
| Modify the PASS condition itself | Same as above, pollutes the verification loop |
| Swallow error directly in catch | Hides the problem, next round may still FAIL but console becomes clean |
| Cross-scope file deletion / architecture change | Belongs to /design scope |
| Hardcode value to bypass | Treats the symptom, not the root cause |
Phase D: Wait for Reload
- HMR scenario:
browser_wait_for(time=2)→browser_navigate(target_url)Force reload - Requires build / restart: use
AskUserQuestionto ask the user whether ready, do not automatically run build (to avoid long occupancy) - SSR / Next.js: follow project conventions (hot reload is usually sufficient; if server component changes are involved, restart is required)
Step 2: HITL Gate (trigger timing see Step 0b: every round starting from round 3)
Before entering Phase A use AskUserQuestion:
Still not passing verification after Round {N-1} ends.
Round Log:
# Verdict Diagnosis summary Files Changed 1 FAIL Login button onClick missing src/Login.tsx 2 FAIL Token header not sent on /api/me src/api/client.ts Currently blocking PASS conditions:
- {failed criterion 1}
- {failed criterion 2}
Options:
- Continue — run 1 more round (maximum up to round 5 hard cap)
- Stop and report — output current state, hand over to human
- Change strategy — describe new direction then re-enter Step 0b
- Hand off to /design — problem exceeds fix scope, needs architecture replanning
When hard cap is reached, even if "Continue" is selected, force stop and output cap-exceeded report.
Handling when option 4 is selected: call /design and pass current round_log and failure evidence as context, to avoid /design starting from scratch.
Step 3: Final Report
Regardless of success / early exit / reaching cap, final output uses unified format:
## Verify-Fix Loop Report
**Status:** PASS | EARLY-EXIT | CAP-EXCEEDED
**Rounds:** N / 5
**Target:** {target_url}
**Allowed paths:** {glob list}
### PASS Criteria
- [x] 預期元素出現
- [x] URL 正確
- [ ] 無 console error ← 仍 FAIL(早退或 cap-exceeded 時)
### Round Log
| # | Verdict | Diagnosis | Files Changed |
|---|---|---|---|
| 1 | FAIL | Login button missing onClick | src/Login.tsx (+5 -2) |
| 2 | FAIL | API returns 401, token header missing | src/api/client.ts (+3 -0) |
| 3 | PASS | — | — |
### Files Modified(累積)
- src/Login.tsx
- src/api/client.ts
### Next Steps
- {若 PASS:建議跑 lint / test / `/pr`}
- {若 EARLY-EXIT:列出選擇的選項與下一步}
- {若 CAP-EXCEEDED:強烈建議轉 /design 重新規劃,附 round_log}
See Also
/playwright-human-in-the-loop— single-operation browser automation (non-loop)/design— architecture replanning when the problem exceeds fix scope/verify-evidence-loop— Evidence verification of technical claims (not code fixes)/pr— Commit + PR automation after successful loop