Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 49 additions & 23 deletions plugins/kernel-cli/skills/debug-browser-session/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ Substitute your session ID for `<SESSION_ID>`.

### Session status
```bash
kernel browsers get <SESSION_ID>
kernel browsers get <SESSION_ID> -o json

# If the normal lookup returns not found, recover the soft-deleted record by session ID.
kernel browsers get <SESSION_ID> --include-deleted -o json
```

The deleted record provides static configuration and timestamps such as `created_at` and `deleted_at`; VM, Playwright, screenshot, curl, log, and live-view commands still require a live session.

### Screenshot the current state
```bash
kernel browsers computer screenshot <SESSION_ID> --to screenshot.png
Expand All @@ -39,20 +44,25 @@ kernel browsers computer screenshot <SESSION_ID> --to screenshot.png
kernel browsers playwright execute <SESSION_ID> "return { url: page.url(), title: await page.title() }"
```

### Read VM log files
### Read browser service logs
```bash
kernel browsers fs read-file <SESSION_ID> --path /var/log/supervisord.log
kernel browsers fs read-file <SESSION_ID> --path /var/log/supervisord/chromium
kernel browsers fs read-file <SESSION_ID> --path /var/log/supervisord/neko
kernel browsers logs stream <SESSION_ID> --source supervisor --supervisor-process chromium --follow=false
kernel browsers logs stream <SESSION_ID> --source supervisor --supervisor-process neko --follow=false
```

### List files in the VM
For other VM logs, list and read files directly:

```bash
kernel browsers fs list-files <SESSION_ID> --path /var/log
kernel browsers fs read-file <SESSION_ID> --path /var/log/supervisord.log
```

### Run commands inside the VM
### Compare Chrome and VM network paths
```bash
# Uses Chrome's TLS fingerprint, cookies, headers, and proxy configuration.
kernel browsers curl <SESSION_ID> https://example.com -I -w 'status=%{http_code} total=%{time_total}\n'

# Uses curl inside the VM instead of Chrome's network stack.
kernel browsers process exec <SESSION_ID> -- curl -I https://example.com
kernel browsers process exec <SESSION_ID> -- cat /etc/resolv.conf
```
Expand All @@ -64,7 +74,7 @@ kernel browsers playwright execute <SESSION_ID> "const cookies = await page.cont

## Browser telemetry events (works even after deletion)

Telemetry only helps if it was enabled before the failure — capture is off by default. When it was on, events captured in the VM stay readable after telemetry is disabled or the session is deleted; every other command in this skill needs a live session.
Telemetry only helps if it was enabled before the failure — capture is off by default. When it was on, captured events stay readable after telemetry is disabled or the session is deleted. A soft-deleted session's static record is also available with `kernel browsers get <SESSION_ID> --include-deleted`; runtime inspection still needs a live session.

The debug-critical categories are console (console output and uncaught exceptions), network (request/response metadata), and page (navigation and lifecycle). High-signal event types: console_error, network_loading_failed, network_response with non-2xx status, system_oom_kill, and monitor_disconnected (telemetry gap — treat following events as incomplete). For the full category and event-type catalog, see the [telemetry categories docs](https://docs.onkernel.com/browsers/telemetry/categories).

Expand All @@ -75,7 +85,7 @@ kernel browsers telemetry events <SESSION_ID> --since 24h --categories console,n
kernel browsers telemetry events <SESSION_ID> --since 24h --types console_error,network_loading_failed
```

`--since` accepts an RFC-3339 timestamp or a duration like `5m`, and **defaults to the last 5 minutes** — a deleted session can't be `get`ed to check its lifetime, so when in doubt use a generous window like `--since 24h` (as in the examples), or the session's created_at if you know it. `--all` walks every page in the window (default is one page of 20 events, `--limit` up to 100, with an `--offset` cursor for manual paging); a `--types` filter walks the whole window automatically. Use `-o json` for full event payloads — the default table shows only sequence, time, category, and type, so anything that depends on event contents (a `network_response` status code, a console error message, a URL) requires json output. The same events are available via the API/SDK (`GET /browsers/{session_id}/telemetry/events`, which has the same 5-minute `since` default) and — if your Kernel MCP server build includes it — the `manage_browsers` tool's `get_telemetry` action (which defaults to the full session window). For a live session, `kernel browsers telemetry stream <SESSION_ID>` tails events as they happen.
`--since` accepts an RFC-3339 timestamp or a duration like `5m`, and **defaults to the last 5 minutes**. When in doubt, use a generous window like `--since 24h` (as in the examples); for a deleted session, use `browsers get --include-deleted` to recover its `created_at` and bound the window precisely. `--all` walks every page in the window (default is one page of 20 events, `--limit` up to 100, with an `--offset` cursor for manual paging); a `--types` filter walks the whole window automatically. Use `-o json` for full event payloads — the default table shows only sequence, time, category, and type, so anything that depends on event contents (a `network_response` status code, a console error message, a URL) requires json output. The same events are available via the API/SDK (`GET /browsers/{session_id}/telemetry/events`, which has the same 5-minute `since` default) and — if your Kernel MCP server build includes it — the `manage_browsers` tool's `get_telemetry` action (which defaults to the full session window). For a live session, `kernel browsers telemetry stream <SESSION_ID>` tails events as they happen.

### Enable capture
```bash
Expand All @@ -97,11 +107,26 @@ Gotchas:
Bot detection is a common cause. Many sites use CDNs like Cloudflare, Imperva, or Akamai that fingerprint browsers and block automation.

Signs of bot detection:
- `curl` works from the VM but Chrome shows an error
- VM `curl` works but the page or `kernel browsers curl` fails
- "Access Denied", captcha pages, or "Checking your browser…" messages
- `stealth: false` in the browser config (check with `kernel browsers get`)
- `stealth: false` in the browser config

Check `kernel browsers get <SESSION_ID> -o json` for `stealth` and an explicit `proxy_id`. If a stealth session is expected to use Kernel's default proxy, isolate that route only after capturing the screenshot, telemetry, and logs:

Solutions: stealth is set at creation and can't be toggled on a live session, so recreate it with `kernel browsers create --stealth`; use profiles with real auth; or try shorter session lifetimes.
```bash
# This mutates live routing.
kernel browsers update <SESSION_ID> --disable-default-proxy
# Reproduce the failing request once and capture the result.
kernel browsers update <SESSION_ID> --disable-default-proxy=false
```

Only run this A/B test when the session owner confirms the default proxy should currently be enabled, because `browsers get` does not expose the prior `disable_default_proxy` state. Do not substitute `--clear-proxy`; that removes an explicit proxy. Restore the default proxy regardless of the result; if direct routing fixed the failure, investigate the proxy path.

Stealth is set at creation and can't be toggled on a live session. If you create a stealth replacement to reproduce, preserve the original session and delete only the diagnostic replacement when done:

```bash
kernel browsers delete <DIAGNOSTIC_SESSION_ID>
```

### Browser not responding
Cause: Chrome process crashed or hung.
Expand All @@ -110,12 +135,12 @@ Solutions: confirm the timeout wasn't reached, look for memory issues in the log

### Page not loading
Cause: network, DNS, or proxy issues.
Check: `curl` from inside the VM, `/etc/resolv.conf` for DNS config, proxy settings if one is configured.
Check: compare `kernel browsers curl` with VM `curl`, inspect `/etc/resolv.conf`, then inspect the configured proxy. A Chrome-only failure points toward Chrome state, TLS fingerprinting, cookies, or its proxy route; failure in both paths points lower in the VM/network stack.

### Live view not working
Cause: Neko/WebRTC issues.
Check: fetch the live view URL with `kernel browsers view <SESSION_ID>` and open it; check Neko logs for connection errors.
Solutions: check for a firewall blocking WebRTC, verify the browser isn't in headless mode.
Check: fetch the live view URL with `kernel browsers view <SESSION_ID>` and open it; read Neko logs with `browsers logs stream`; verify `headless` is false in `browsers get`.
Solutions: check for a firewall blocking WebRTC and compare whether Playwright and screenshots still work. If they do, the browser is healthy and the fault is isolated to live view.

## Expected log entries (normal operation)

Expand All @@ -127,21 +152,22 @@ These are normal and don't indicate problems:

## Debugging checklist

- [ ] Session exists and is active
- [ ] Active session found, or deleted metadata recovered with `--include-deleted`
- [ ] Screenshot shows expected content (or reveals the error)
- [ ] Current URL is as expected
- [ ] Supervisor logs show all services running
- [ ] Network connectivity works (curl test)
- [ ] Chrome-stack and VM curl results compared
- [ ] No critical errors in chromium logs
- [ ] Cookies/session state are correct
- [ ] Telemetry events checked for console_error / network_loading_failed / system events (especially if the session is gone)
- [ ] Any temporary default-proxy change restored; any diagnostic replacement deleted

## Suggested order

1. Get browser info to confirm the session is active.
2. Take a screenshot to see the current state.
3. Check the page URL to see whether it's on an error page.
4. Test network connectivity if seeing connection errors.
5. Review logs for specific error patterns.
1. Get browser info; retry with `--include-deleted` if it is gone.
2. Preserve evidence: screenshot, URL/title, telemetry, then logs.
3. Compare Chrome-stack curl with VM curl for connection failures.
4. Reproduce once; only then run a temporary default-proxy A/B test if relevant.
5. Restore changed routing and delete any diagnostic replacement session.

If the session no longer exists, telemetry events are the only remaining signaland only if telemetry was enabled while it ran.
If the session is deleted, recover its static metadata with `browsers get --include-deleted`; telemetry is the remaining runtime signal, and only if capture was enabled while it ran.
57 changes: 33 additions & 24 deletions plugins/kernel-cli/skills/diff-profile-archives/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: diff-profile-archives
description: Compare two Kernel profile archives to investigate behavioral differences. Use when an issue (e.g. login failure, captcha, broken automation, vendor mismatch) reproduces on one profile but not another, or when a "good" vs "bad" profile needs to be diffed. Takes two profile IDs/names plus an issue description, downloads both archives via the Kernel CLI, and surfaces differences in cookies, storage, preferences, extensions, and login state that could explain the issue.
description: Compare two Kernel profile snapshots to investigate behavioral differences. Use when an issue (e.g. login failure, captcha, broken automation, vendor mismatch) reproduces on one profile but not another, or when a "good" vs "bad" profile needs to be diffed. Takes two profile IDs/names plus an issue description, downloads and extracts both profiles via the Kernel CLI, and surfaces differences in cookies, storage, preferences, extensions, and login state that could explain the issue.
---

# Diff Kernel Profile Archives

Compare the actual contents of two Kernel browser profiles to identify state differences that could explain a reported issue. The skill downloads both profile archives via the Kernel CLI, decompresses them, and produces a structured diff focused on the surfaces most likely to drive divergent behavior: cookies, local/session storage, preferences, extension state, login data, and history.
Compare the actual contents of two Kernel browser profiles to identify state differences that could explain a reported issue. The skill downloads and extracts both profiles via the Kernel CLI, then produces a structured diff focused on the surfaces most likely to drive divergent behavior: cookies, local/session storage, preferences, extension state, login data, and history.

## Inputs

Expand All @@ -19,9 +19,9 @@ If any of these are missing, prompt for them before proceeding. The issue descri

## Prerequisites

- Kernel CLI installed and authenticated (`kernel auth status`)
- `KERNEL_API_KEY` set, or the user is logged in via `kernel auth login`
- `zstd`, `tar`, `sqlite3`, and `jq` available (install via `apt-get install -y zstd sqlite3 jq` if needed)
- Kernel CLI installed and authenticated (`kernel auth`)
- `KERNEL_API_KEY` set, or the user is logged in via `kernel login`
- `sqlite3` and `jq` available (install via `apt-get install -y sqlite3 jq` if needed)
- ~500MB free disk space per profile (profiles can be large)

## Workflow
Expand All @@ -35,29 +35,38 @@ mkdir -p a b diff
echo "Workspace: $WORKDIR"
```

### Step 2: Download both archives
### Step 2: Resolve and download both profiles

```bash
# Replace <A> and <B> with the profile IDs or names from the user
kernel profiles download <A> --pretty --to a/profile.zip
kernel profiles download <B> --pretty --to b/profile.zip
# Replace these with the profile IDs or exact names from the user.
PROFILE_A='<working-id-or-name>'
PROFILE_B='<broken-id-or-name>'

# Record metadata and confirm the identifiers resolve to two distinct profile IDs.
kernel profiles get "$PROFILE_A" -o json | tee diff/profile-a.json
kernel profiles get "$PROFILE_B" -o json | tee diff/profile-b.json
A_ID=$(jq -r '.id' diff/profile-a.json)
B_ID=$(jq -r '.id' diff/profile-b.json)
[ "$A_ID" != "$B_ID" ] || { echo "Both inputs resolve to $A_ID" >&2; exit 1; }

# --to is an extraction directory, not an archive filename.
kernel profiles download "$PROFILE_A" --to a
kernel profiles download "$PROFILE_B" --to b
```

`--pretty` pretty-prints JSON files (e.g. `Preferences`) so they diff cleanly line-by-line. Always pass it.
Profile names and IDs are resolved in the CLI's active project scope. If the user specifies a project, use the same `--project <id-or-name>` on every `profiles get` and `profiles download` command.

### Step 3: Extract

The archive is **Zstandard-compressed TAR despite the `.zip` extension**. Standard unzip will fail.
The CLI requests `tar.zst` by default, streams the response, decompresses it, and extracts it directly under `--to`. It does not leave an archive file. Keep the default for the smaller transfer. Use `--format tar` only to bypass zstd decompression while debugging a format/decompression problem; the CLI still extracts the response into `--to`:

```bash
for side in a b; do
zstd -d "$side/profile.zip" -o "$side/profile.tar"
tar -xf "$side/profile.tar" -C "$side"
rm "$side/profile.tar"
done
kernel profiles download "$PROFILE_A" --format tar --to a
```

After extraction each side typically contains a Chrome user-data-dir layout (e.g. `Default/` with `Cookies`, `Local Storage/`, `Preferences`, etc.). If the layout differs, run `find a -maxdepth 3 -type d` to orient yourself before continuing.
If the CLI reports that a profile has no saved data yet, stop: the profile must first be used in a browser session so Kernel can capture state.

### Step 3: Inspect the extracted layout

Each side typically contains a Chrome user-data-dir layout (e.g. `Default/` with `Cookies`, `Local Storage/`, `Preferences`, etc.). Run `find a b -maxdepth 3 -type d | sort` to orient yourself before continuing.

### Step 4: Triage by issue description

Expand Down Expand Up @@ -123,8 +132,8 @@ for side in a b; do
echo "(no Preferences)" > "diff/preferences-$side.json"
continue
fi
# Already pretty-printed thanks to --pretty
cp "$PREF" "diff/preferences-$side.json"
# Normalize key ordering and indentation before diffing.
jq -S . "$PREF" > "diff/preferences-$side.json"
done
diff -u diff/preferences-a.json diff/preferences-b.json > diff/preferences.diff || true
```
Expand Down Expand Up @@ -327,7 +336,7 @@ Use this template:

### Step 8: Cleanup

Profile archives can contain credentials. Always remove the workspace when done:
Extracted profile data can contain credentials. Always remove the workspace when done:

```bash
rm -rf "$WORKDIR"
Expand All @@ -337,8 +346,8 @@ If the user wants to keep the artifacts for further investigation, tell them the

## Notes & gotchas

- **Archive format**: `.zip` extension is misleading — it's `zstd`-compressed TAR. Use `zstd -d` then `tar -xf`, not `unzip`.
- **`--pretty` is mandatory** for meaningful JSON diffs. Without it, `Preferences` is a single line and diffs are unreadable.
- **Download destination**: `--to` names an extraction directory. Do not give it a `.zip` filename and do not run `unzip`, `zstd`, or `tar`; the CLI extracts the requested `tar.zst` (default) or `tar` response itself.
- **Normalize JSON locally**: `profiles download` has no `--pretty` flag. Use `jq -S .` before diffing JSON files so formatting and key order do not create noise.
- **Chrome profile path varies**: most data lives under `Default/`, but newer Chrome moved cookies to `Default/Network/Cookies`. Use `find` instead of hardcoding paths.
- **SQLite locks**: profiles are downloaded as snapshots, so DBs aren't locked — but if a query errors with "database is locked", make sure you're querying the extracted copy, not a path inside an open archive.
- **Don't compare `Cache/`, `Code Cache/`, `GPUCache/`, `Service Worker/CacheStorage/`** — these are noise and will produce huge meaningless diffs.
Expand Down
Loading