.clinerules/debug-harness.md
HTTP-controlled debugger for the VSCode extension at src/dev/debug-harness/server.ts.
# Build extension first if needed (protos + esbuild):
bun run protos && IS_DEV=true bun esbuild.mjs
# Launch (skip-build if already built):
bun src/dev/debug-harness/server.ts --skip-build --auto-launch
# In another terminal:
curl localhost:19229/api -d '{"method":"status"}'
The debugee runs with CLINE_DIR=~/.cline2 by default, separate from your real ~/.cline.
This prevents the debugee's logout from logging out the debugger, and vice versa.
Override with --cline-dir /tmp/test-dir. Check with status() → clineDir.
The debugee runs with CLINE_CAPTURE_BROWSER=1, which intercepts openExternal() in
src/utils/env.ts. URLs are captured instead of opening a real browser:
$CLINE_DIR/data/debug-captured-urls.jsonl/captured-url on the harness serveroauth.captured_urlsoauth.captured_urls {clear?} — URLs the debugee tried to openoauth.read_stored_token — Check auth token presence in secrets.jsonoauth.simulate_callback {path, code?, state?, provider?, token?} — Build vscode:// callback URIoauth.read_captured_urls_file — Read on-disk JSONL of captured URLsFor Cline OAuth (SDK local callback): The SDK starts a local HTTP server, the auth URL
is captured. To complete: open the captured URL in a real browser (it redirects back to the
SDK's callback server), OR extract the callback port and curl http://127.0.0.1:PORT/callback?code=....
For MCP/Provider OAuth (vscode:// URI): The redirect goes to a vscode:// URI.
oauth.simulate_callback only builds the URI — it does not deliver it, and the ESM
extension host can't require() the handler. To actually deliver the callback, call the
debug-only hook via ext.evaluate (with awaitPromise: true):
globalThis.__clineHandleUri("vscode://saoudrizwan.claude-dev/...?code=...&state=...").
It runs the same SharedUriHandler.handleUri as VSCode's real URI handler and exists only
when CLINE_CAPTURE_BROWSER is set (the harness always sets it; never ships in prod).
For end-to-end MCP OAuth, get a real code from the local MCP OAuth test server
(bun run dev:mcp-oauth-test-server).
Don't try to find/click small sidebar icons. Use VSCode commands via command palette.
Registered in src/registry.ts:
| Command | View |
|---|---|
cline.accountButtonClicked | Account / sign-in |
cline.historyButtonClicked | Task history |
cline.settingsButtonClicked | Settings |
cline.mcpButtonClicked | MCP servers |
cline.plusButtonClicked | New task (chat) |
cline.worktreesButtonClicked | Worktrees |
curl localhost:19229/api -d '{"method":"ui.command_palette","params":{"command":"cline.accountButtonClicked"}}'
All via POST localhost:19229/api with {"method":"...", "params":{...}}:
launch / shutdown — lifecycleui.screenshot — screenshot to /tmp/cline-debug/; returns {path} — use read_file on the path to examine, do NOT open the file (Preview.app covers the VSCode window)ui.open_sidebar — open the Cline sidebarext.set_breakpoint {file, line, condition?} — breakpoint by source file (sourcemap-resolved)ext.evaluate {expression, callFrameId?} — eval in extension hostext.resume / ext.step_over / ext.step_into — steppingext.call_stack — inspect when pausedweb.evaluate {expression} — eval in webviewweb.post_message {message} — send postMessage to extension host via exposed vsCodeApiwait_for_pause {timeout?} — block until breakpoint hitui.locator {role?, testId?, text?, frame?} — Playwright locator (auto-retries on stale sidebar frame)ui.react_input {text, selector?, clear?, submit?} — set React textarea value via execCommand('insertText'); works reliably across multiple tasksui.send_message {text, images?, files?, responseType?} — send chat message bypassing the textarea entirely (via gRPC postMessage)ui.command_palette {command} — run VSCode command# 1. Launch
curl localhost:19229/api -d '{"method":"launch","params":{"skipBuild":true}}'
# 2. Open sidebar + dismiss overlays (ALWAYS do this first)
curl localhost:19229/api -d '{"method":"ui.open_sidebar"}'
curl localhost:19229/api -d '{"method":"web.evaluate","params":{"expression":"document.querySelectorAll(\".sr-only\").forEach(el => el.parentElement?.click())"}}'
# 3. Navigate to view
curl localhost:19229/api -d '{"method":"ui.command_palette","params":{"command":"cline.accountButtonClicked"}}'
# 4. Check captured OAuth URLs if testing auth
curl localhost:19229/api -d '{"method":"oauth.captured_urls"}'
# 5. Verify
curl localhost:19229/api -d '{"method":"ui.screenshot"}'
ui.open_sidebar, before any other interaction or screenshot. May need to run twice:
curl localhost:19229/api -d '{"method": "ui.open_sidebar"}'
curl localhost:19229/api -d '{"method": "web.evaluate", "params": {"expression": "document.querySelectorAll(\".sr-only\").forEach(el => el.parentElement?.click())"}}'
ui.screenshot and ui.sidebar_screenshot save PNGs to /tmp/cline-debug/ and return the {path}. Use read_file on that path to examine screenshots. Running open <path> launches Preview.app on macOS which covers the VSCode window.connect_webview may fail depending on Electron version. web.evaluate still works via Playwright's frame.evaluate() fallback.../src/extension.ts in the sourcemap. The resolver handles this, but if a file isn't found, use ext.source_files to see exact paths.See src/dev/debug-harness/README.md for full API reference.