Skip to content

docs(skills): Make browser debugging work with any browser tool - #4428

Merged
ntucker merged 47 commits into
masterfrom
claude/project-thread-jnnzva
Oct 21, 2026
Merged

ntucker merged 47 commits into
masterfrom
claude/project-thread-jnnzva

Conversation

@ntucker

@ntucker ntucker commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Nathaniel · project thread

Stacked on #4435 (globalThis.__DC__); merge that first.

Before: the React and Vue skills' debugging guide was "Debugging with Chrome DevTools MCP". Agents had to paste a 34-line shim after every page load, then import('/src/...') endpoints, which only works on Vite. Agents on Playwright MCP, Claude in Chrome or Cursor's browser had no reason to think the guide applied to them.

After: the guide works with any browser tool that runs JavaScript in the page and reads the store through __DC__, so the snippets use the Controller API the skills already teach:

// run in page — response for a key
() => {
  const c = __DC__.controller, state = c.getState();
  const a = __DC__.actions.findLast(a => a.args && a.key?.includes('/todos'));
  if (!a) return { error: 'key not in the action log; repeat the fetch' };
  const { data, expiryStatus, expiresAt } = c.getResponse(a.endpoint, ...a.args, state);
  return { key: a.key, data, expiryStatus, expiresAt, error: c.getError(a.endpoint, ...a.args, state)?.message };
}
  • devtools-debugging.md is the core: snippets, the three ways a tool can run one (function, awaited expression, expression) and fallbacks for tools without network or console readers. Its snippets dropped from 449 to 236 lines.
  • Each browser-*.md (Cursor desktop and cloud, Chrome DevTools MCP, Playwright MCP, Claude in Chrome) is a thin adapter: install, open the page, which way it runs snippets, and that tool's quirks.
  • yarn check:browser-snippets (in the regenerate workflow) runs every snippet each way in Chromium against an app built from the workspace sources. It reads the ways and their parameters from the page, so the docs can't drift from what works.
  • .cursor/rules/agent-browser-docs.mdc holds the snippet contract and which tools are live-verified.

How: edited docs/core/_agents/, added the setup sources to both skills' references.json, regenerated references with yarn build:skills, and added checkBrowserSnippets.mjs plus website/scripts/workspaceSources.mjs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa


Note

Low Risk
Documentation, generated skill references, and CI verification only; no production runtime or API behavior changes in this diff.

Overview
Generalizes agent browser debugging so React/Vue skills and docs work with any tool that runs page JavaScript (Cursor browser, Playwright MCP, Chrome DevTools MCP, Claude in Chrome), not only Chrome DevTools MCP.

The core guide devtools-debugging.md is rewritten around globalThis.__DC__ (__DC__.controller, __DC__.actions): the old per-load debug shim and import('/src/...') endpoint snippets are removed in favor of action-log–driven snippets and three execution modes (function, awaited expression, expression), plus performance / console-capture fallbacks when a tool has no network or console APIs. Thin browser-*.md adapters document install and per-tool quirks; skills references.json and debugging sections point agents at the right adapter.

Agent skills get clearer front-matter descriptions; setup now steers projects toward AGENTS.md (with Reactive Data Client ownership text). Schema skill adds shouldReorder / collection-filter guidance and a new older-copy-overwrites eval.

CI and maintainability: yarn check:browser-snippets (Chromium via Playwright) runs every in-page snippet each documented way against a workspace-built test app; regenerate workflow installs Chromium and runs it. Shared workspaceSources.mjs bundles browser test apps; agent-browser-docs rules document the snippet contract and verification status.

Reviewed by Cursor Bugbot for commit 1002a5d. Bugbot is set up for automated code reviews on this repo. Configure here.

@ntucker ntucker self-assigned this Oct 19, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T03:12:57.452782Z 1002a5d Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@changeset-bot

changeset-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 1002a5d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

ntucker commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

@cursor Please add Cursor's built-in browser to this PR's browser debugging guide, and verify it works end to end.

  1. Find out what Cursor's built-in browser (the agent's browser tools) can do: in particular, whether any of its tools runs JavaScript in the page and returns the result, and how it reads network requests and console logs.
  2. Run a Data Client example app in dev mode (for example examples/todo-app), open it in the built-in browser, and follow docs/core/_agents/devtools-debugging.md: install the Step 1 shim, read state (Step 2), and read the action log (Step 3).
  3. Add docs/core/_agents/browser-cursor.md, matching the other browser-*.md setup references (what runs a snippet, how to pass it, the network and console readers), add a Cursor row to the Browser Tools table, list it in both .agents/skills/data-client-react/references.json and data-client-vue/references.json and in each SKILL.md reference list, then run yarn build:skills and commit the generated references.
  4. If the built-in browser can't run page JavaScript, say so in that reference and give the working Cursor setup instead (Playwright MCP or Chrome DevTools MCP in .cursor/mcp.json), verified with the same steps.

Reply here with what you ran and what each step returned.


Generated by Claude Code

@cursor

cursor Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Cursor's built-in browser can run the debugging snippets. It has no function-style evaluate tool. browser_cdp with method Runtime.evaluate runs an expression, so the snippet has to be called.

This cloud agent has no cursor-ide-browser tools, so I ran the same Runtime.evaluate call in Chrome against examples/todo-app at http://localhost:8080/.

  • Step 1 returned { ok: true, actionLog: "shim", stateKeys: ["entities","endpoints","indexes","meta","entitiesMeta","optimistic","lastReset"] }. Passing the function without calling it returned a function and no value.
  • Step 2 raw state returned 20 Todo entities and GET https://jsonplaceholder.typicode.com/todos?userId=1. import('/src/resources/TodoResource.ts') 404'd on webpack.
  • Step 3 was [] until invalidateAll, then it included rdc/invalidateall, rdc/fetch for Todo.getList, and rdc/setresponse. Network responses for the jsonplaceholder user and todo requests were status 200.

docs/core/_agents/browser-cursor.md is in the Browser Tools table and both skills. Cloud agents still use Playwright MCP in .cursor/mcp.json. Details are on PR #4428.

Open in Web Open in Cursor 

ntucker commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): LGTM at 812c588. Nothing to change in this PR.

What I actually checked

  • I read src/tools/script.ts on chrome-devtools-mcp main. In function mode it wraps the source as (\n${source}\n) and calls the result. The old IIFE shim would have run (so it still installed) and then failed with fn is not a function. The plain () => {} is the right shape, so this is a real fix, not just wording.
  • I searched master (GitHub code search). After this PR, evaluate_script and user-chrome-devtools show up only in the files this PR rewrites. The other "Chrome DevTools MCP" hits are dated blog posts and the i18n copies, which translation sync handles.
  • What I didn't check: I didn't run any of the three tools, and I didn't read the Playwright MCP or Claude in Chrome sources.

Non-blocking notes

  1. Claude in Chrome with async snippets: if you wrap an async () => {...} snippet as an IIFE, it returns a promise. The Step 2 module snippets and the Step 4 invalidate/setResponse snippets are all async. Agents will follow browser-claude-in-chrome.md literally, so either check that javascript_tool awaits the promise, or say in that file that async snippets aren't verified yet.
  2. Each tool's names are now written in two places: the Browser Tools table row and that tool's setup file. With a Cursor row on the way, that's where drift will show up first (for example, @playwright/mcp is still pre-1.0 and installed @latest). Keeping both is fine, since the table saves the agent a file read. If a tool rename ever ends up fixed in only one place, that's the signal to let the setup file own the names and have the table just link to it. I'm not planning a follow-up until that happens.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Docs preview: https://docs-site-pr-4428-data-client.vercel.app

Built from 1002a5d merged into master (this build).

ntucker commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks. On the two notes:

  1. Claude in Chrome with async snippets: fixed in 4184ea3. browser-claude-in-chrome.md now covers the case where javascript_tool hands back a promise instead of the value: store the value on a global with .then(...) and read it in a second call. That works whether or not the tool awaits. The Browser Tools intro also says Claude in Chrome needs the snippet called, not passed bare. A post-push review flagged that gap too. Same commit: DevToolsManager.md no longer files Expo MCP under "browser tool".
  2. Names written in both the table and the setup files: I'm keeping both, as you suggested, until a rename actually drifts.

Generated by Claude Code

ntucker commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): Still LGTM at 4184ea3. Nothing to change in this PR.

What I checked: I compared 812c588...4184ea3. It's one commit: the Claude in Chrome async note in browser-claude-in-chrome.md (source plus both generated copies), the rewritten Browser Tools intro, and the DevToolsManager.md parenthesis that stops calling Expo MCP a browser tool. The generated React and Vue copies match the docs/core/_agents/ source. That closes my note 1. I still haven't run Claude in Chrome myself.

One optional nit: the global-stash pattern can hand back a stale value. If a later async snippet rejects, .then(r => ...) never runs, so __DC_RESULT__ still holds the previous snippet's result, and an agent following the steps literally will read it as the new answer. One way to make it safe is to capture errors too: .then(r => (globalThis.__DC_RESULT__ = r), e => (globalThis.__DC_RESULT__ = { error: String(e) })). Fine to land as is if you'd rather keep the line short.

ntucker commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Took the nit in d3b2613. The async recipe now clears __DC_RESULT__ before it runs, stores a rejection as { error }, and says to read until the value is no longer undefined. A failed snippet, or a read before the promise settles, can no longer return the previous snippet's result.


Generated by Claude Code

@cursor

cursor Bot commented Oct 19, 2026

Copy link
Copy Markdown

Added docs/core/_agents/browser-cursor.md and a Cursor row in the Browser Tools table. This cloud agent has no cursor-ide-browser tools (GetDynamicTools on that namespace: not found; no browser_* tools in the catalog), so the snippets were run with Chrome's Runtime.evaluate, which is the call browser_cdp forwards.

What the built-in browser can do. Cursor's browser docs list navigate, click, type, scroll, screenshot, console output (log files), and network traffic (Agent panel). They do not name an evaluate tool. Cursor staff describe browser_cdp as the raw-script path (Runtime.evaluate approval prompts; do not send Page.reload — it reloads the Cursor window). Cloud agents and CI do not get these tools; the new reference points them at Playwright MCP in .cursor/mcp.json.

What I ran. npm ci and npm start -- --port 8080 in examples/todo-app (webpack compiled, http://localhost:8080/). The app uses published @data-client/core@0.18.2, which has __DC_CONTROLLERS__ and no __DC_ACTIONS__. Chrome 154 headless, then Runtime.evaluate with expression: (snippet)(), awaitPromise: true, returnByValue: true.

  • Controllers once data settled: { size: 1, hasActions: "undefined", title: "todo-app", endpointCount: 1 } while users were in, then todos followed.
  • Passing Step 1 bare (not called): { type: "function", value: {} }.
  • Step 1 called: { ok: true, actionLog: "shim", stateKeys: ["entities","endpoints","indexes","meta","entitiesMeta","optimistic","lastReset"] }.
  • Step 2 raw state: { entityTypes: ["User","[User]","Todo","[Todo]"], endpointCount: 3, metaCount: 3, optimisticCount: 0, lastReset: 0, todoCount: 20, sampleEndpoint: "GET https://jsonplaceholder.typicode.com/todos?userId=1" }.
  • Step 2 import('/src/resources/TodoResource.ts'): TypeError: Failed to fetch dynamically imported module: http://localhost:8080/src/resources/TodoResource.ts (webpack 404). Same call with awaitPromise: false returned { type: "object", value: {} }.
  • Step 3 right after the shim: [] (shim log; load-time fetches are not in it).
  • invalidateAll for /todos: "todo endpoints invalidated".
  • Step 3 after that included { type: "rdc/invalidateall" }, { type: "rdc/fetch", key: "GET https://jsonplaceholder.typicode.com/todos?userId=1", endpoint: "Todo.getList", args: [{ userId: 1 }] }, and a matching rdc/setresponse.
  • Network: Network.responseReceived showed https://jsonplaceholder.typicode.com/users, /users/1, and /todos?userId=1 at status 200. performance.getEntriesByType('resource') returned those URLs and durations, with no status codes.

@ntucker

ntucker commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): Still LGTM at 69dbef4, nothing blocking.

The new Cursor browser page follows the existing per-tool setup pattern (source doc, references.json entry, generated React/Vue copies, one table row), so it adds a row rather than a new shape. Calling the snippet as (<snippet>)() with awaitPromise also avoids the __DC_RESULT__ polling dance Claude in Chrome needs.

What I checked: I diffed 571c2bb...69dbef4 and confirmed the generated React/Vue browser-cursor.md copies match the source apart from the header comment. I read Cursor's Browser docs, which confirm browser_navigate, that logs go to files the agent greps, and that network traffic is "currently only available in the Agent panel." What I could not verify: those public docs never name browser_cdp or any way to run page JavaScript. That means the Runtime.evaluate behavior, the empty-object result without awaitPromise, and the warning that Page.reload reloads the Cursor window all depend on your own run in Cursor. If those came from a real session, great.

Two optional nits:

  1. In the Browser Tools table, every other row's Network log cell names a tool the agent can call. "Agent panel" names a UI. Something like "browser_network_requests if listed, else performance entries" would match step 3 and give the agent something it can actually call.
  2. The Playwright MCP JSON now appears in both browser-cursor.md and browser-playwright-mcp.md. Linking to the Install section instead would keep one copy if the package args ever change.

ntucker commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

I took both nits in c994784. Cursor's Network log cell now names readers the agent can call (browser_network_requests if listed, else performance entries). browser-cursor.md now links to Playwright MCP's Install section instead of repeating the JSON.

On the browser_cdp details: none of them came from a real desktop Cursor session. Cursor's cloud agent took them from Cursor staff posts, and checked them with plain Chrome Runtime.evaluate. I've asked Nathaniel to try it in desktop Cursor before this merges.


Generated by Claude Code

ntucker commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): Still LGTM at 2552dc5. Splitting network and console into steps 3 and 4 reads better, and pointing the fallback at the meta error in devtools-debugging Step 3 ("Filter errors") is the right anchor. I checked that section exists and returns meta error message and status.

One optional accuracy nit, not blocking: "responseStatus is 0 for cross-origin APIs" is broader than the spec. Per MDN's responseStatus page, it's 0 when the CORS check fails or the response is opaque; a cross-origin API that sends Access-Control-Allow-Origin exposes its real status. Something like "responseStatus is 0 when CORS blocks the response, and blocked requests can be missing" would keep the same conclusion (check the meta error) without telling agents a CORS-enabled API never reports a status.

Validated: read the one-commit delta from 81f42a2 across all six files; the React/Vue generated browser-cursor.md and devtools-debugging.md hunks match the source; read docs/core/_agents/devtools-debugging.md at head to confirm the Step 3 link target. The responseStatus behavior is from MDN, not a live browser run.

ntucker commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

I took your responseStatus wording ("0 when CORS blocks the response") in b45a774. The same commit gives the performance fallback a snippet that maps entries to plain { url, duration, status } objects. PerformanceResourceTiming fields are prototype getters, so returnByValue on the raw entries would come back as empty objects.


Generated by Claude Code

ntucker commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

@cursor Please check the cloud-agent path from b138977 in a fresh run, and push fixes if anything doesn't work.

  1. This branch now has .cursor/mcp.json with a headless Playwright MCP. Check that it loaded: list the Playwright tools you have (browser_navigate, browser_evaluate, browser_network_requests, browser_console_messages). If they're missing, find out why. Is MCP config read by cloud agents at all? Does Chrome need installing (.cursor/environment.json)? Then fix the config, or the "Cloud agent" section of docs/core/_agents/browser-cursor.md, so it works.
  2. Run examples/todo-app in dev mode and follow docs/core/_agents/devtools-debugging.md using only those Playwright tools: Step 1 shim, Step 2 raw state, Step 3 action log after invalidateAll, plus the network log.
  3. If you change docs, run yarn build:skills and commit the generated references.

Reply with the tool list you saw and what each step returned.


Generated by Claude Code

@cursor

cursor Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Playwright never showed up in this cloud agent's tool list. A search for playwright and browser_ found nothing: no browser_navigate, browser_evaluate, browser_network_requests, or browser_console_messages. The run's MCP config is only the dashboard servers (Aws-mcp and turso). Cloud agents do not read .cursor/mcp.json, and this image has no .cursor/environment.json. Chrome 154 is already installed.

The committed command does work when started directly. npx -y @playwright/mcp@0.0.83 --headless listed 25 tools, including those four, with no extra browser install. On examples/todo-app at http://localhost:8080/:

  • Step 1 returned { ok: true, actionLog: "shim", stateKeys: ["entities","endpoints","indexes","meta","entitiesMeta","optimistic","lastReset"] }.
  • Step 2 raw state returned User, [User], Todo, [Todo], with 3 endpoints and 3 meta entries.
  • After invalidateAll for /todos, Step 3 returned rdc/invalidateall, then rdc/fetch and rdc/setresponse for Todo.getList at GET https://jsonplaceholder.typicode.com/todos?userId=1.
  • browser_network_requests showed those jsonplaceholder user and todo requests at status 200. The console had one error, a favicon.ico 404.

docs/core/_agents/browser-cursor.md now says to enable that same server in the agents MCP dropdown. The generated skill references are committed on PR #4428.

Open in Web Open in Cursor 

@ntucker

ntucker commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): Not LGTM yet at b138977. I'm waiting on the cloud-agent check you just started rather than asking for a change now, because the main new claim isn't backed by Cursor's docs.

The claim in question. The new Cloud agent section says cloud agents "load the project's .cursor/mcp.json". Cursor's public docs don't say that. Cloud Agents explicitly says cloud agents run the repo's .cursor/hooks.json. For MCP, both that page and Capabilities only mention servers set up for your team or yourself in the cursor.com/agents MCP dropdown or Dashboard → Plugins & MCPs (stdio is supported there). Neither page mentions a repo mcp.json, and the Cloud Agents API takes inline mcpServers per run. The same Cloud Agents page also says cloud agents "can also use computers to control the desktop and browser", so "no built-in browser tools" may be too strong, even if the earlier run had no cursor-ide-browser namespace.

Depending on what the check finds:

  • The Playwright tools show up from the committed file: keep the approach. Then fix the small drift below.
  • They don't: the committed .cursor/mcp.json doesn't help cloud agents, and the section should say to add Playwright MCP from the cursor.com/agents MCP dropdown (or the team dashboard) instead. In that case I'd also drop .cursor/mcp.json from this PR. On its own it only adds a project MCP server for everyone who opens this repo in desktop Cursor, which is a repo environment change this docs PR doesn't need.

Small drift either way (optional): browser-cursor.md now repeats the Playwright mcp.json block that c994784 removed in favor of linking to Playwright's Install section. It uses @playwright/mcp@latest, while the committed file pins 0.0.83 (the version the tool names were checked against). That's three copies with two versions. Linking to the Install section and saying "add --headless", or pointing at the repo's own .cursor/mcp.json, keeps it to one source. Also, Renovate won't bump a version inside mcp.json args, so the pin will go stale quietly. That's fine for now; I'd only add a regex manager if the pin actually bites.

What I actually checked: I compared b45a774...b138977 (one commit: the Cursor desktop/cloud split in browser-cursor.md across the source and React/Vue generated copies, the two Cursor rows in the Browser Tools table, the new .cursor/mcp.json, and a debugging.md wording tweak). I read the current browser-playwright-mcp.md Install section on the head. I fetched Cursor's cloud-agent, capabilities and setup docs and searched them for mcp.json, .cursor/ and MCP: only hooks.json and environment.json are described as read from the repo. I didn't run a cloud agent myself, so how it actually behaves is still unverified until your run reports back. CI is mostly green, and Bugbot and regenerate were still pending when I looked.

@cursor

cursor Bot commented Oct 20, 2026

Copy link
Copy Markdown

Checked b138977 on this cloud agent. Playwright was not in the agent's tool list. The server from .cursor/mcp.json does run on the image, so the docs now say to enable it in the agents MCP dropdown instead of expecting the file to load.

Agent tool list. A search of this run's tools for playwright and browser_ returned nothing. No browser_navigate, browser_evaluate, browser_network_requests, or browser_console_messages. The process MCP config for the run lists the dashboard servers only (Aws-mcp and turso), not playwright. Cloud agent docs say MCP servers are added in the dropdown at cursor.com/agents. There is no .cursor/environment.json. Google Chrome 154 is already on the image, and the Playwright server did not need a separate browser install.

The committed command, started by hand. npx -y @playwright/mcp@0.0.83 --headless (Playwright 1.64.0) listed 25 tools, including the four above. browser_evaluate takes function. browser_network_requests takes filter and static. browser_console_messages takes level.

Against examples/todo-app at http://localhost:8080/:

  • browser_navigate: page title todo-app.
  • Step 1: { "ok": true, "actionLog": "shim", "stateKeys": ["entities","endpoints","indexes","meta","entitiesMeta","optimistic","lastReset"] }.
  • Step 2 raw state: { "entityTypes": ["User","[User]","Todo","[Todo]"], "endpointCount": 3, "metaCount": 3, "optimisticCount": 0, "lastReset": 0 }.
  • invalidateAll for /todos: "todo endpoints invalidated".
  • Step 3 after that: rdc/invalidateall, then rdc/fetch and rdc/setresponse for Todo.getList at GET https://jsonplaceholder.typicode.com/todos?userId=1 with args: [{ "userId": 1 }].
  • browser_network_requests with filter jsonplaceholder|todos|users: GET /users, /users/1, and /todos?userId=1, each [200].
  • browser_console_messages: 2 messages, 1 error, the error being favicon.ico 404.

docs/core/_agents/browser-cursor.md now says a cloud agent does not read .cursor/mcp.json. The same JSON has to be enabled in the agents MCP dropdown. yarn build:skills regenerated the references.

@ntucker

ntucker commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): LGTM at 4a80960. My hold from b138977 is resolved. The cloud-agent run gave real evidence: Playwright wasn't in its tool list, the run's MCP config held only the dashboard servers, and the same command started by hand worked end to end against examples/todo-app. The Cloud agent section now matches both that run and Cursor's docs, which say to add servers through the cursor.com/agents MCP dropdown or the team dashboard.

Two notes, both non-blocking and your call:

  • .cursor/mcp.json is still in the PR, but it was added for cloud agents, and they don't read it. What's left is a project Playwright server for everyone who opens this repo in desktop Cursor, and desktop agents already have cursor-ide-browser with browser_cdp. If you want that for contributors, keep it. Otherwise I'd drop it and leave this as a docs-only PR.
  • The Playwright JSON now appears three times: .cursor/mcp.json and browser-cursor.md pin 0.0.83, while browser-playwright-mcp.md Install still says @latest with no --headless. That's a small drift. If it starts to matter, pick one version or have browser-cursor.md link to the Install section and just add --headless.

What I actually checked: I compared b138977...4a80960 (one commit: the Cloud agent section and table row in browser-cursor.md, plus the Cursor cloud row in devtools-debugging.md, across the source and the React/Vue generated copies, with identical patches). I read the cloud agent's report on this PR and confirmed that .cursor/mcp.json and the @latest Install block are still on the head. I didn't run a cloud agent myself. One thing nobody has tested yet is enabling the server through the dropdown itself. The run proved only that the image can run the command, and the dropdown step relies on Cursor's docs, which say stdio servers are supported there. When I looked, Bugbot, regenerate, website and tree-passed were still pending, and everything else was green.

@ntucker

ntucker commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): LGTM at 2d229ce. My hold from b138977 is lifted.

The cloud-agent run confirmed the concern: Playwright never showed up in its tool list from the committed .cursor/mcp.json. The two new commits fix both sides of that. They delete the file, so the repo no longer ships config that does nothing, and they rewrite the Cloud agent section (source plus the React and Vue copies) to say the server has to be enabled in the MCP dropdown at cursor.com/agents.

What I checked. I read the diff from b138977 to 2d229ce with gh api compare: 7 files, with .cursor/mcp.json removed and the matching wording updated in browser-cursor.md and devtools-debugging.md across all three trees. I also checked the new wording against Cloud Agent capabilities. It says to add personal servers through the MCP dropdown at cursor.com/agents, says team servers live under Dashboard -> Plugins & MCPs, and says stdio servers run inside the VM. That matches what the PR now says.

What I only read. The claims that the server works with no .cursor/environment.json and that it exposes those four tools come from the cloud agent's run report above. I didn't rerun it.

Optional nit. The cloud section pins @playwright/mcp@0.0.83, which is the version that was actually tested, but browser-playwright-mcp.md Install still uses @latest. That's fine if the pin is on purpose. Just be aware the two will drift.

Bugbot and regenerate were still running when I looked.

@ntucker

ntucker commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): Still LGTM at d98ddea. Nothing to change in this PR.

What I checked: I compared be16cc4...d98ddea. It's one commit that touches only browser-cursor.md (the source plus the React and Vue copies). The generated copies match the source apart from the header and table padding, and the React and Vue copies are identical apart from the header. The devtools-debugging links it uses still resolve: Step 3 "Filter errors" and Step 4 (Mutate State, which has invalidateAll).

What I actually ran: I pulled the new console-capture snippet out of the source doc and ran it in Node with a stubbed addEventListener. A second call is a no-op that returns the current count. It records Error stacks, objects, circular objects (they fall back to String), unhandled rejections and error events with a null error. Past 200 entries it trims, so the buffer stays bounded. The original console methods still get called.

What I only read and did not verify: the commit says it was checked against Cursor 3.25.9's cursor-ide-browser. I couldn't confirm that browser_cdp returns no CDP events, that rejections show up in exceptionDetails with result.value set to {}, or that Page.reload ends the turn. None of those claims changes the recommended path, which is still browser_navigate plus a function snippet.

For later, not this PR: the Cursor page now carries its own in-page console shim. If a second tool ever needs in-page console capture, that's the point to move it into devtools-debugging as a shared step instead of copying it.

@ntucker

ntucker commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Cursor desktop agent: cursor-ide-browser interfaces as observed

Ran the Desktop agent path in docs/core/_agents/browser-cursor.md against examples/todo-app using only cursor-ide-browser, with no Playwright MCP. Doc fixes are in d98ddea. Everything below is quoted verbatim from tool inputs and outputs.

Environment

  • Cursor 3.25.9, commit 5d4df965746c5d09740842fc0f1331ba1c157830, VS Code 1.128.0, build date 2026-10-15T21:52:24.891Z, x64 (from resources/app/product.json; cursor --version printed 3.25.9 / 5d4df965746c5d09740842fc0f1331ba1c157830 / x64)
  • Windows host, workspace in WSL2 (Linux 6.18.54.1)
  • App: examples/todo-app via npm ci && npm start (it isn't a yarn workspace; yarn start there fails with Usage Error: The nearest package directory ... doesn't seem to be part of the project). webpack-dev-server at http://localhost:8080/, using the published @data-client/* packages, so Step 1 reports actionLog: 'shim'.

Every cursor-ide-browser result ends with a second content item that the client renders as a trailing non-JSON line, e.g. Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}. It is quoted below as received. A parser has to read only the first content item.

Approval

No call asked for approval, including browser_cdp with Runtime.evaluate, Network.enable, Log.enable, Runtime.enable, Network.getResponseBody, Page.reload, plus browser_navigate, browser_lock, browser_tabs, browser_snapshot. All returned (or, for Page.reload, executed) without a prompt.

browser_cdp (a): Step 1 as (<snippet>)() with awaitPromise and returnByValue

Input:

{"method": "Runtime.evaluate", "params": {"expression": "(() => {\n  const NAME = undefined;\n  const controllers = globalThis.__DC_CONTROLLERS__;\n  if (!controllers?.size) return { error: 'No controllers registered' };\n  if (!NAME && controllers.size > 1)\n    return { error: 'Set NAME to one of', keys: [...controllers.keys()] };\n  const key = NAME ?? controllers.keys().next().value;\n  const ctrl = controllers.get(key);\n  if (!ctrl) return { error: 'Controller not found', keys: [...controllers.keys()] };\n  const stateKeys = Object.keys(ctrl.getState());\n\n  const builtIn = globalThis.__DC_ACTIONS__?.get(key);\n  if (builtIn || globalThis.__DC_CTRL__ !== ctrl) globalThis.__DC_ACTION_LOG__ = builtIn ?? [];\n  globalThis.__DC_CTRL__ = ctrl;\n  if (builtIn) return { ok: true, actionLog: 'built-in', stateKeys };\n  // dispatch is rebound if the middleware chain is rebuilt, so check the wrapper, not the alias\n  if (ctrl._dispatch.__dcDebugShim) return { ok: true, actionLog: 'shim', stateKeys };\n\n  const MAX_LOG = 200;\n  const origDispatch = ctrl._dispatch;\n  const shim = (action) => {\n    if (globalThis.__DC_CTRL__ === ctrl) {\n      const log = globalThis.__DC_ACTION_LOG__;\n      log.push(action);\n      if (log.length > MAX_LOG) log.splice(0, MAX_LOG / 2);\n    }\n    return origDispatch(action);\n  };\n  shim.__dcDebugShim = true;\n  ctrl._dispatch = shim;\n\n  return { ok: true, actionLog: 'shim', stateKeys };\n})()", "awaitPromise": true, "returnByValue": true}}

Output:

{
  "result": {
    "type": "object",
    "value": {
      "actionLog": "shim",
      "ok": true,
      "stateKeys": [
        "entities",
        "endpoints",
        "indexes",
        "meta",
        "entitiesMeta",
        "optimistic",
        "lastReset"
      ]
    }
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

(b) Step 1 snippet passed bare (same params)

Input:

{"method": "Runtime.evaluate", "params": {"expression": "() => {\n  const NAME = undefined;\n  const controllers = globalThis.__DC_CONTROLLERS__;\n  if (!controllers?.size) return { error: 'No controllers registered' };\n  if (!NAME && controllers.size > 1)\n    return { error: 'Set NAME to one of', keys: [...controllers.keys()] };\n  const key = NAME ?? controllers.keys().next().value;\n  const ctrl = controllers.get(key);\n  if (!ctrl) return { error: 'Controller not found', keys: [...controllers.keys()] };\n  const stateKeys = Object.keys(ctrl.getState());\n\n  const builtIn = globalThis.__DC_ACTIONS__?.get(key);\n  if (builtIn || globalThis.__DC_CTRL__ !== ctrl) globalThis.__DC_ACTION_LOG__ = builtIn ?? [];\n  globalThis.__DC_CTRL__ = ctrl;\n  if (builtIn) return { ok: true, actionLog: 'built-in', stateKeys };\n  // dispatch is rebound if the middleware chain is rebuilt, so check the wrapper, not the alias\n  if (ctrl._dispatch.__dcDebugShim) return { ok: true, actionLog: 'shim', stateKeys };\n\n  const MAX_LOG = 200;\n  const origDispatch = ctrl._dispatch;\n  const shim = (action) => {\n    if (globalThis.__DC_CTRL__ === ctrl) {\n      const log = globalThis.__DC_ACTION_LOG__;\n      log.push(action);\n      if (log.length > MAX_LOG) log.splice(0, MAX_LOG / 2);\n    }\n    return origDispatch(action);\n  };\n  shim.__dcDebugShim = true;\n  ctrl._dispatch = shim;\n\n  return { ok: true, actionLog: 'shim', stateKeys };\n}", "awaitPromise": true, "returnByValue": true}}

Output:

{
  "result": {
    "type": "function",
    "value": {}
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

The function is not called. A follow-up check confirmed nothing was installed:

{"method": "Runtime.evaluate", "params": {"expression": "(() => typeof globalThis.__DC_CTRL__)()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "string",
    "value": "undefined"
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

(c) Async snippet without awaitPromise

Input:

{"method": "Runtime.evaluate", "params": {"expression": "(async () => { await new Promise(r => setTimeout(r, 10)); return { ok: true, n: __DC_CTRL__.getState().lastReset }; })()", "returnByValue": true}}

Output:

{
  "result": {
    "type": "object",
    "value": {}
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

(d) Snippet that throws

Input:

{"method": "Runtime.evaluate", "params": {"expression": "(() => { throw new Error('boom from snippet'); })()", "awaitPromise": true, "returnByValue": true}}

Output:

{
  "exceptionDetails": {
    "columnNumber": 9,
    "exception": {
      "className": "Error",
      "description": "Error: boom from snippet\n    at <anonymous>:1:16\n    at <anonymous>:1:50",
      "objectId": "-8794747995531637297.4.2",
      "subtype": "error",
      "type": "object"
    },
    "exceptionId": 1,
    "lineNumber": 0,
    "scriptId": "26",
    "text": "Uncaught"
  },
  "result": {
    "className": "Error",
    "description": "Error: boom from snippet\n    at <anonymous>:1:16\n    at <anonymous>:1:50",
    "objectId": "-8794747995531637297.4.1",
    "subtype": "error",
    "type": "object"
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

A rejected awaited promise is different: result.value is {} next to exceptionDetails, so a reader that only checks result.value sees an empty success. This was Step 2's import() snippet on webpack:

{"method": "Runtime.evaluate", "params": {"expression": "(async () => {\n  const mod = await import('/src/resources/Todo.ts');\n  const state = __DC_CTRL__.getState();\n  const { data, expiryStatus, expiresAt } = __DC_CTRL__.getResponse(\n    mod.TodoResource.getList,\n    {},\n    state,\n  );\n  return { data, expiryStatus, expiresAt };\n})()", "awaitPromise": true, "returnByValue": true}}
{
  "exceptionDetails": {
    "columnNumber": 0,
    "exception": {
      "className": "TypeError",
      "description": "TypeError: Failed to fetch dynamically imported module: http://localhost:8080/src/resources/Todo.ts",
      "objectId": "-8794747995531637297.4.3",
      "subtype": "error",
      "type": "object"
    },
    "exceptionId": 2,
    "lineNumber": 0,
    "text": "Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: http://localhost:8080/src/resources/Todo.ts"
  },
  "result": {
    "type": "object",
    "value": {}
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

(e) Non-serializable return values

DOM node:

{"method": "Runtime.evaluate", "params": {"expression": "(() => document.body)()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "object",
    "value": {}
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

Function:

{"method": "Runtime.evaluate", "params": {"expression": "(() => () => 1)()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "function",
    "value": {}
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

Page.reload

It reloads the whole Cursor window, as the doc says. The tool call never returned a response: the agent's turn ended mid-call. When the session resumed, the pending call was sent again and the window reloaded a second time. The browser tab (same viewId 103107) survived, the page reloaded with it, and the shim was gone afterward (typeof globalThis.__DC_CTRL__ → "undefined", performance.timeOrigin 1792472226972 = 2026-10-20T04:57:06.972Z). The webpack dev server running in WSL was unaffected.

Input (as logged):

{"method":"Page.reload","params":{}}

Output: none received. %APPDATA%\Cursor\logs\20261020T003113\window2\exthost\output_logging_20261020T004637_4b9ee008\1-Cursor IDE Browser Automation.log:

[2026-10-20T04:51:13.092Z] [INFO] [general] Executing tool: browser_cdp with args: {"method":"Page.reload","params":{}}
[2026-10-20T04:51:13.134Z] [INFO] [general] Browser Automation MCP Provider disposed

...\output_logging_20261020T005116_581396eb\1-Cursor IDE Browser Automation.log (the resumed session):

[2026-10-20T04:51:19.457Z] [INFO] [general] Cursor Browser Automation extension activated
[2026-10-20T04:51:19.458Z] [INFO] [general] Using Browser MCP provider
[2026-10-20T04:51:19.458Z] [INFO] [general] Browser Automation MCP Provider initialized with direct execution
[2026-10-20T04:51:19.459Z] [INFO] [general] injectBrowserUIScript called
[2026-10-20T04:51:19.536Z] [INFO] [general] Browser UI script injected successfully
[2026-10-20T04:57:05.650Z] [INFO] [general] Executing tool: browser_cdp with args: {"method":"Page.reload","params":{}}
[2026-10-20T04:57:05.699Z] [INFO] [general] Browser Automation MCP Provider disposed

For an automated test, assert that a harness never sends Page.reload. Testing it live kills the run that sent it.

browser_navigate to the same URL

With the Step 1 shim and the console capture (below) installed:

{"url": "http://localhost:8080/"}
### Action: snapshot
- Total refs: 1
- Interactive refs: 0
- Browser View ID: 103107

### Page state
- Page URL: http://localhost:8080/
- Page Title: todo-app
- Page Snapshot:
```yaml
- role: document
  name: todo-app
  ref: root
  children:
    - role: heading
      name: Todos
      ref: e0
      level: 1
```Unsupported content type "metadata": {"type":"metadata","viewId":"103107","title":"todo-app","url":"http://localhost:8080/","locked":true}

It is a full page load, and the shim does not survive:

{"method": "Runtime.evaluate", "params": {"expression": "(() => ({ ctrl: typeof globalThis.__DC_CTRL__, log: typeof globalThis.__DC_ACTION_LOG__, console: typeof globalThis.__DC_CONSOLE__, controllers: globalThis.__DC_CONTROLLERS__?.size, nav: performance.getEntriesByType('navigation')[0]?.type, resources: performance.getEntriesByType('resource').length }))()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "object",
    "value": {
      "console": "undefined",
      "controllers": 1,
      "ctrl": "undefined",
      "log": "undefined",
      "nav": "navigate",
      "resources": 21
    }
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

The first browser_navigate of the session (no tabs open; browser_tabs {"action": "list"} returned Open tabs:) returned a snapshot of about:blank, not the target page. A browser_snapshot right after showed http://localhost:8080/ loaded:

### Action: snapshot
- Total refs: 0
- Interactive refs: 0
- Browser View ID: 103107

### Page state
- Page URL: about:blank
- Page Title: 
- Page Snapshot:
```yaml
- role: document
  ref: root
```Unsupported content type "metadata": {"type":"metadata","viewId":"103107","title":"","url":"about:blank","locked":false}

Network readers

cursor-ide-browser has no network tool (no browser_network_requests). browser_cdp returns only command results, not CDP events, so Network.enable succeeds but leaves nothing readable, and Network.getResponseBody needs a requestId that only events carry:

{"method": "Network.enable", "params": {}}
{}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}
{"method": "Network.getResponseBody", "params": {"requestId": "1"}}
Error: Tool execution error. Error invoking remote method 'vscode:webviewSendCDPCommand': Error: No resource with given identifier found

The working reader is the doc's performance fallback through browser_cdp. Its schema is the snippet's own return shape, { url: string, duration: number, status: number }[]. Sample after Step 3's invalidateAll (the second todos?userId=1 is the refetch). The avatar and font loads show status: 0 even though they loaded: they're cross-origin no-cors responses, so 0 doesn't mean CORS blocked anything. The doc now says so.

{"method": "Runtime.evaluate", "params": {"expression": "(() => performance.getEntriesByType('resource').map(e => ({ url: e.name, duration: e.duration, status: e.responseStatus })))()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "object",
    "value": [
      {
        "duration": 107.40000003576279,
        "status": 0,
        "url": "https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Outfit:wght@300;400;500;600;700&display=swap"
      },
      {
        "duration": 7.100000023841858,
        "status": 200,
        "url": "http://localhost:8080/runtime~main.js"
      },
      {
        "duration": 8.899999976158142,
        "status": 200,
        "url": "http://localhost:8080/main.css"
      },
      {
        "duration": 50.60000002384186,
        "status": 200,
        "url": "http://localhost:8080/main.js"
      },
      {
        "duration": 3.199999988079071,
        "status": 200,
        "url": "http://localhost:8080/node_modules_data-client_react_lib_components_DevToolsButton_js.chunk.js"
      },
      {
        "duration": 126.69999998807907,
        "status": 200,
        "url": "https://jsonplaceholder.typicode.com/users/1"
      },
      {
        "duration": 131,
        "status": 200,
        "url": "https://jsonplaceholder.typicode.com/users"
      },
      {
        "duration": 125.09999996423721,
        "status": 200,
        "url": "https://jsonplaceholder.typicode.com/todos?userId=1"
      },
      {
        "duration": 84.19999998807907,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=5"
      },
      {
        "duration": 70.79999995231628,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=11"
      },
      {
        "duration": 72.69999998807907,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=6"
      },
      {
        "duration": 72.79999995231628,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=9"
      },
      {
        "duration": 75.79999995231628,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=10"
      },
      {
        "duration": 84.09999996423721,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=8"
      },
      {
        "duration": 88.79999995231628,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=7"
      },
      {
        "duration": 70.40000003576279,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=14"
      },
      {
        "duration": 83.30000001192093,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=12"
      },
      {
        "duration": 85.10000002384186,
        "status": 0,
        "url": "https://i.pravatar.cc/64?img=13"
      },
      {
        "duration": 4.100000023841858,
        "status": 404,
        "url": "http://localhost:8080/src/resources/Todo.ts"
      },
      {
        "duration": 56.90000003576279,
        "status": 200,
        "url": "https://jsonplaceholder.typicode.com/todos?userId=1"
      }
    ]
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

Console readers

cursor-ide-browser has no console tool (no browser_console_messages). The doc's previous fallback, "read the browser log files Cursor writes for the session", doesn't work on 3.25.9: C:\Users\natma\.cursor\browser-logs is empty and was last modified Dec 2025. The only browser log I found, 1-Cursor IDE Browser Automation.log, records tool calls, not page output. Log.enable and Runtime.enable return {} and their events never reach the agent:

{"method": "Log.enable", "params": {}}
{}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}
{"method": "Runtime.enable", "params": {}}
{}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

The doc now gives an in-page capture (__DC_CONSOLE__) installed with browser_cdp. Its schema is { level: 'log'|'info'|'warn'|'error'|'debug'|'uncaught'|'unhandledrejection', time: number, text: string }[]. It only records output after it is installed and is cleared by a page load. Install:

{"method": "Runtime.evaluate", "params": {"expression": "(() => {\n  if (globalThis.__DC_CONSOLE__) return { ok: true, count: __DC_CONSOLE__.length };\n  const log = (globalThis.__DC_CONSOLE__ = []);\n  const text = (a) => {\n    if (a instanceof Error) return a.stack ?? String(a);\n    if (typeof a === 'string') return a;\n    try { return JSON.stringify(a); } catch { return String(a); }\n  };\n  const push = (level, args) => {\n    log.push({ level, time: Date.now(), text: args.map(text).join(' ') });\n    if (log.length > 200) log.splice(0, 100);\n  };\n  for (const level of ['log', 'info', 'warn', 'error', 'debug']) {\n    const orig = console[level];\n    console[level] = (...args) => { push(level, args); return orig.apply(console, args); };\n  }\n  addEventListener('error', (e) => push('uncaught', [e.error ?? e.message]));\n  addEventListener('unhandledrejection', (e) => push('unhandledrejection', [e.reason]));\n  return { ok: true, count: 0 };\n})()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "object",
    "value": {
      "count": 0,
      "ok": true
    }
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

Probe:

{"method": "Runtime.evaluate", "params": {"expression": "(() => { console.warn('dc-probe-warn', { a: 1 }); setTimeout(() => { throw new Error('dc-probe-uncaught'); }); Promise.reject(new Error('dc-probe-rejection')); return 'ok'; })()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "string",
    "value": "ok"
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

Read:

{"method": "Runtime.evaluate", "params": {"expression": "(() => __DC_CONSOLE__.slice(-20))()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "object",
    "value": [
      {
        "level": "warn",
        "text": "dc-probe-warn {\"a\":1}",
        "time": 1792471845156
      },
      {
        "level": "uncaught",
        "text": "Error: dc-probe-uncaught\n    at <anonymous>:1:76",
        "time": 1792471845157
      },
      {
        "level": "unhandledrejection",
        "text": "Error: dc-probe-rejection\n    at <anonymous>:1:127\n    at <anonymous>:1:176",
        "time": 1792471845158
      }
    ]
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

Steps 2 and 3 (passed as written)

Step 2 raw state, "check endpoint metadata":

{"method": "Runtime.evaluate", "params": {"expression": "(() => {\n  const state = __DC_CTRL__.getState();\n  const key = Object.keys(state.meta).find(k => k.includes('/todos'));\n  return key ? { key, ...state.meta[key] } : 'no meta found';\n})()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "object",
    "value": {
      "date": 1792471716111,
      "expiresAt": 1792475316111,
      "fetchedAt": 1792471715984,
      "key": "GET https://jsonplaceholder.typicode.com/todos?userId=1"
    }
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

Step 3, after __DC_CTRL__.invalidateAll({ testKey: key => key.includes('/todos') }) (returned {"result": {"type": "string", "value": "todo endpoints invalidated"}}), "read recent actions":

{"method": "Runtime.evaluate", "params": {"expression": "(() => __DC_ACTION_LOG__.slice(-20).map(a => ({\n  type: a.type,\n  key: a.key,\n  endpoint: a.endpoint?.name,\n  args: a.args,\n  fetchedAt: a.meta?.fetchedAt,\n  error: a.error ? (a.response?.message || true) : undefined,\n})))()", "awaitPromise": true, "returnByValue": true}}
{
  "result": {
    "type": "object",
    "value": [
      {
        "type": "rdc/invalidateall"
      },
      {
        "args": [
          {
            "userId": 1
          }
        ],
        "endpoint": "Todo.getList",
        "fetchedAt": 1792471758987,
        "key": "GET https://jsonplaceholder.typicode.com/todos?userId=1",
        "type": "rdc/fetch"
      },
      {
        "args": [
          {
            "userId": 1
          }
        ],
        "endpoint": "Todo.getList",
        "fetchedAt": 1792471758987,
        "key": "GET https://jsonplaceholder.typicode.com/todos?userId=1",
        "type": "rdc/fetch"
      },
      {
        "args": [
          {
            "userId": 1
          }
        ],
        "endpoint": "Todo.getList",
        "fetchedAt": 1792471758988,
        "key": "GET https://jsonplaceholder.typicode.com/todos?userId=1",
        "type": "rdc/fetch"
      },
      {
        "args": [
          {
            "userId": 1
          }
        ],
        "endpoint": "Todo.getList",
        "fetchedAt": 1792471758988,
        "key": "GET https://jsonplaceholder.typicode.com/todos?userId=1",
        "type": "rdc/fetch"
      },
      {
        "args": [
          {
            "userId": 1
          }
        ],
        "endpoint": "Todo.getList",
        "fetchedAt": 1792471758987,
        "key": "GET https://jsonplaceholder.typicode.com/todos?userId=1",
        "type": "rdc/setresponse"
      }
    ]
  }
}Unsupported content type "metadata": {"type":"metadata","viewId":"103107"}

Every cursor-ide-browser tool and schema, as received

16 tools: browser_cdp, browser_click, browser_drag, browser_fill, browser_get_bounding_box, browser_highlight, browser_lock, browser_mouse_click_xy, browser_navigate, browser_press_key, browser_scroll, browser_select_option, browser_snapshot, browser_tabs, browser_take_screenshot, browser_type. The same 16 are listed in the workspace's mcps/cursor-ide-browser/tools/*.json. Below is the full discovery response for {"namespace":"cursor-ide-browser"}, verbatim:

Discovery response (581 lines)
{
  "mode": "namespace",
  "namespace": "cursor-ide-browser",
  "namespaceStatus": "ready",
  "namespaceDescription": "The cursor-ide-browser MCP server provides a Cursor-owned browser tab plus a raw Chrome DevTools Protocol command tool. CORE WORKFLOW: 1. Start by understanding the user's goal and what success looks like on the page. 2. Use browser_tabs with action \"list\" to inspect open tabs and URLs before acting. 3. Use browser_navigate to create or navigate the target tab. Omit the position parameter for background automation so focus is preserved. 4. Use browser_lock before longer automation on an existing tab, then browser_lock with action \"unlock\" when finished. 5. Use browser_snapshot for accessibility context and browser_take_screenshot for visual verification. 6. Use browser_click, browser_type, browser_fill, browser_select_option, browser_press_key, browser_scroll, and browser_drag for page interactions. 7. Use browser_highlight and browser_get_bounding_box for visual grounding and coordinate diagnostics. 8. Use browser_cdp for page inspection, profiling, runtime evaluation, DOM/CSS queries, and performance data. AVOID RABBIT HOLES: 1. Do not repeat the same failing action more than once without new evidence such as a fresh snapshot, a different ref, a changed page state, or a clear new hypothesis. 2. IMPORTANT: If four attempts fail or progress stalls, stop acting and report what you observed, what blocked progress, and the most likely next step. 3. Prefer gathering evidence over brute force. If the page is confusing, use browser_snapshot, browser_take_screenshot, or CDP inspection before trying more actions. 4. If you encounter a blocker such as login, passkey/manual user interaction, permissions, captchas, destructive confirmations, missing data, or an unexpected state, stop and report it instead of improvising repeated actions. 5. Do not get stuck in wait-action-wait loops. Every retry should be justified by something newly observed. CRITICAL - Lock/unlock workflow: 1. browser_lock requires an existing browser tab - you CANNOT call browser_lock with action: \"lock\" before browser_navigate 2. Correct order: browser_navigate -> browser_lock({ action: \"lock\" }) -> (interactions) -> browser_lock({ action: \"unlock\" }) 3. If a browser tab already exists (check with browser_tabs list), call browser_lock with action: \"lock\" FIRST before any interactions 4. Only call browser_lock with action: \"unlock\" when completely done with ALL browser operations for this turn IMPORTANT - Waiting strategy: When waiting for page changes, prefer short CDP polling loops with Runtime.evaluate, DOM queries, Page lifecycle signals, or browser_snapshot checks rather than a single long wait. CDP USAGE: - Use browser_cdp with a DevTools Protocol method and params object, for example Runtime.evaluate, DOM.getDocument, CSS.getComputedStyleForNode, Profiler.start/stop, Performance.getMetrics, Log.enable, and Network.enable. - Do not use browser_cdp with CDP Input.* methods. They are denied because they are focus-sensitive in Electron webviews and can route input to Cursor UI instead of the browser page. - Use browser_click, browser_type, browser_fill, browser_select_option, browser_press_key, browser_scroll, and browser_drag for clicks, typing, filling inputs, selecting options, keyboard actions, scrolling, and drag-and-drop. - Use Runtime.evaluate for advanced DOM-scoped interactions that the dedicated browser tools do not cover. - For profiling, call Profiler.enable, Profiler.start, reproduce the behavior, then Profiler.stop. The profile is saved to a file and returned as a log_file; read that file only when you need to inspect details. - For JavaScript evaluation, prefer Runtime.evaluate with returnByValue when possible. - Device emulation you set with Emulation.setDeviceMetricsOverride, Emulation.setTouchEmulationEnabled, or Emulation.setUserAgentOverride (for example for a phone-sized screenshot) lasts only for the current turn: it is cleared when your turn ends, and the user can reset it from the browser tab at any time. Send Emulation.clearDeviceMetricsOverride yourself once you are done with it within a turn. - Some browser-wide or sensitive CDP methods are denied, especially cookie, storage, permission, download, target-management, filesystem-backed file-input commands, system-level commands, and CDP navigation/history navigation commands. - Large CDP responses are saved to files instead of being inlined. Prefer using the returned file path over immediately stuffing large payloads into context; read focused sections only when needed. VISION: - browser_take_screenshot attaches an image result that the model can inspect. CDP Page.captureScreenshot returns data inside JSON and should not replace browser_take_screenshot when visual verification is needed. NOTES: - browser_snapshot returns snapshot YAML and is the main source of truth for page structure. - Refs are opaque handles tied to the latest browser_snapshot for that tab. - Iframe content is not accessible - only elements outside iframes can be interacted with. - When you stop to report a blocker, include the current page, the target you were trying to reach, the blocker you observed, and the best next action. If the blocker requires manual user interaction, ask the user to take over at that point rather than assuming it in advance.",
  "tools": [
    {
      "tool": "browser_cdp",
      "description": "Send a Chrome DevTools Protocol command to the target browser tab. Do not use CDP Input.* methods; use dedicated browser tools for clicks, text input, key presses, scrolling, and drag-and-drop. Browser-wide, storage, cookie, permission, download, target-management, and system-level commands are denied.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "method": {
            "type": "string",
            "description": "CDP method name, for example Runtime.evaluate, DOM.getDocument, Profiler.start, or Performance.getMetrics."
          },
          "params": {
            "type": "object",
            "description": "CDP params object. Omit or pass {} when the command takes no params."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after the CDP command completes. Defaults to false."
          }
        },
        "required": [
          "method"
        ]
      }
    },
    {
      "tool": "browser_click",
      "description": "Click an element by ref from browser_snapshot. Use this instead of CDP Input.* methods.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "Element ref from browser_snapshot."
          },
          "element": {
            "type": "string",
            "description": "Human-readable description of the element."
          },
          "offsetX": {
            "type": "number",
            "description": "Optional x offset from the element center."
          },
          "offsetY": {
            "type": "number",
            "description": "Optional y offset from the element center."
          },
          "doubleClick": {
            "type": "boolean",
            "description": "When true, double-click the element."
          },
          "button": {
            "type": "string",
            "enum": [
              "left",
              "right",
              "middle"
            ],
            "description": "Mouse button. Defaults to left."
          },
          "modifiers": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "Control",
                "Shift",
                "Alt",
                "Meta",
                "ControlOrMeta"
              ]
            },
            "description": "Optional modifier keys."
          },
          "holdDurationMs": {
            "type": "number",
            "description": "Optional mouse hold duration before release."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after the click completes. Defaults to false."
          }
        },
        "required": [
          "ref"
        ]
      }
    },
    {
      "tool": "browser_drag",
      "description": "Drag an element by ref to another ref or viewport coordinates.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "sourceRef": {
            "type": "string",
            "description": "Source element ref from browser_snapshot."
          },
          "targetRef": {
            "type": "string",
            "description": "Optional target element ref from browser_snapshot."
          },
          "targetX": {
            "type": "number",
            "description": "Optional target viewport x coordinate."
          },
          "targetY": {
            "type": "number",
            "description": "Optional target viewport y coordinate."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after drag completes. Defaults to false."
          }
        },
        "required": [
          "sourceRef"
        ]
      }
    },
    {
      "tool": "browser_fill",
      "description": "Set the value of an input, textarea, or contenteditable element by ref.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "Element ref from browser_snapshot."
          },
          "value": {
            "type": "string",
            "description": "Value to set."
          },
          "element": {
            "type": "string",
            "description": "Human-readable description of the element."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after filling completes. Defaults to false."
          }
        },
        "required": [
          "ref",
          "value"
        ]
      }
    },
    {
      "tool": "browser_get_bounding_box",
      "description": "Get the viewport bounding box for an element ref.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "Element ref from browser_snapshot."
          },
          "element": {
            "type": "string",
            "description": "Human-readable description of the element."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          }
        },
        "required": [
          "ref"
        ]
      }
    },
    {
      "tool": "browser_highlight",
      "description": "Highlight an element by ref in the browser page for visual grounding.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "Element ref from browser_snapshot."
          },
          "element": {
            "type": "string",
            "description": "Human-readable description of the element."
          },
          "durationMs": {
            "type": "number",
            "description": "Highlight duration in milliseconds. Defaults to 2000."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          }
        },
        "required": [
          "ref"
        ]
      }
    },
    {
      "tool": "browser_lock",
      "description": "Lock or unlock the browser to control whether the user can interact while you work. Set action to \"lock\" or \"unlock\". When locked, the user can still click \"Take Control\" to unlock if needed.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "lock",
              "unlock"
            ],
            "description": "Whether to lock or unlock the browser for user interaction."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          }
        },
        "required": [
          "action"
        ]
      }
    },
    {
      "tool": "browser_mouse_click_xy",
      "description": "Click at viewport coordinates. Prefer browser_click with refs when possible.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "description": "Viewport x coordinate."
          },
          "y": {
            "type": "number",
            "description": "Viewport y coordinate."
          },
          "button": {
            "type": "string",
            "enum": [
              "left",
              "right",
              "middle"
            ],
            "description": "Mouse button. Defaults to left."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after the click completes. Defaults to false."
          }
        },
        "required": [
          "x",
          "y"
        ]
      }
    },
    {
      "tool": "browser_navigate",
      "description": "Navigate to a URL. By default reuses an existing tab; set newTab: true to open in a new tab.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "The URL to navigate to"
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "position": {
            "type": "string",
            "enum": [
              "active",
              "side"
            ],
            "description": "Only set when the user explicitly asks to reveal, show, focus, or open the browser visibly. Set to \"active\" for visible/revealed browser UI, or \"side\" if the user mentions \"side\", \"beside\", \"side panel\", or \"side by side\". Omit this parameter for background automation so focus is preserved."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after navigation completes. Defaults to false."
          },
          "newTab": {
            "type": "boolean",
            "description": "When true, creates a new tab before navigating instead of reusing an existing tab. Defaults to false."
          }
        },
        "required": [
          "url"
        ]
      }
    },
    {
      "tool": "browser_press_key",
      "description": "Press a key in the browser page using DOM keyboard events.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Key to press, for example Enter, Escape, Tab, ArrowDown, or a single character."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after the key press completes. Defaults to false."
          }
        },
        "required": [
          "key"
        ]
      }
    },
    {
      "tool": "browser_scroll",
      "description": "Scroll the page, a scrollable container, or an element into view. Use this instead of CDP Input.* wheel events.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "Optional element ref from browser_snapshot."
          },
          "direction": {
            "type": "string",
            "enum": [
              "up",
              "down",
              "left",
              "right"
            ],
            "description": "Scroll direction."
          },
          "amount": {
            "type": "number",
            "description": "Scroll amount in pixels. Defaults to 300."
          },
          "deltaX": {
            "type": "number",
            "description": "Explicit horizontal scroll delta."
          },
          "deltaY": {
            "type": "number",
            "description": "Explicit vertical scroll delta."
          },
          "scrollIntoView": {
            "type": "boolean",
            "description": "When true, scroll the ref into view."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after scrolling completes. Defaults to false."
          }
        },
        "required": []
      }
    },
    {
      "tool": "browser_select_option",
      "description": "Select one or more options in a select element by ref.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "Element ref from browser_snapshot."
          },
          "values": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Option values or labels to select."
          },
          "element": {
            "type": "string",
            "description": "Human-readable description of the element."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after selection completes. Defaults to false."
          }
        },
        "required": [
          "ref",
          "values"
        ]
      }
    },
    {
      "tool": "browser_snapshot",
      "description": "Capture accessibility snapshot of the current page, this is better than screenshot",
      "inputSchema": {
        "type": "object",
        "properties": {
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "interactive": {
            "type": "boolean",
            "description": "When true, only include interactive elements in the snapshot. Defaults to false."
          },
          "maxDepth": {
            "type": "number",
            "description": "Maximum depth for snapshot output. Defaults to 20."
          },
          "compact": {
            "type": "boolean",
            "description": "When true, outputs a more compact snapshot format. Defaults to false."
          },
          "selector": {
            "type": "string",
            "description": "Optional CSS selector to scope the snapshot to a subtree."
          },
          "includeDiff": {
            "type": "boolean",
            "description": "When true, include a diff vs the previous snapshot for this tab. Defaults to false."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after snapshot completes. Defaults to false."
          }
        },
        "required": []
      }
    },
    {
      "tool": "browser_tabs",
      "description": "List, create, close, or select a browser tab",
      "inputSchema": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "list",
              "new",
              "close",
              "select"
            ],
            "description": "Operation to perform"
          },
          "index": {
            "type": "number",
            "description": "Tab index. Required for \"select\". Optional for \"close\" (defaults to current tab)."
          },
          "position": {
            "type": "string",
            "enum": [
              "active",
              "side"
            ],
            "description": "Only set for action \"new\" when the user explicitly asks to reveal, show, focus, or open the browser visibly. Set to \"active\" for visible/revealed browser UI, or \"side\" if the user mentions \"side\", \"beside\", \"side panel\", or \"side by side\". Omit this parameter for background automation so focus is preserved."
          }
        },
        "required": [
          "action"
        ]
      }
    },
    {
      "tool": "browser_take_screenshot",
      "description": "Take a screenshot of the current page. You can't perform actions based on the screenshot, use browser_snapshot for actions.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Image format for the screenshot. Default is png."
          },
          "filename": {
            "type": "string",
            "description": "File name to save the screenshot to. Defaults to page-{timestamp}.{png|jpeg} if not specified."
          },
          "element": {
            "type": "string",
            "description": "Description of the element, if taking a screenshot of an element"
          },
          "ref": {
            "type": "string",
            "description": "CSS selector for the element, if taking a screenshot of an element"
          },
          "fullPage": {
            "type": "boolean",
            "description": "When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          }
        },
        "required": []
      }
    },
    {
      "tool": "browser_type",
      "description": "Type text into an input, textarea, or contenteditable element by ref.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string",
            "description": "Element ref from browser_snapshot."
          },
          "text": {
            "type": "string",
            "description": "Text to type."
          },
          "element": {
            "type": "string",
            "description": "Human-readable description of the element."
          },
          "clear": {
            "type": "boolean",
            "description": "When true, clear existing text first."
          },
          "submit": {
            "type": "boolean",
            "description": "When true, press Enter after typing."
          },
          "slowly": {
            "type": "boolean",
            "description": "When true, type character by character."
          },
          "viewId": {
            "type": "string",
            "description": "Target browser tab ID. If omitted, uses the last interacted tab."
          },
          "take_screenshot_afterwards": {
            "type": "boolean",
            "description": "When true, takes a screenshot after typing completes. Defaults to false."
          }
        },
        "required": [
          "ref",
          "text"
        ]
      }
    }
  ]
}

Haiku runs read SKILL.md but never opened devtools-debugging, so the
first moves (compare store to server, find the action that wrote it,
recheck every entity after a fix) now live inline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
…t the cause

Haiku benchmark runs on a bug-fix task never opened a listed skill, and
fixed stale data with merge/shouldUpdate overrides or by passing a
sort to push. Now:
- the setup skill's CLAUDE.md line tells agents to read the matching
  data-client skill before changing or debugging @data-client code
- react/vue descriptions name the symptoms (wrong, stale, missing,
  reverting data) and the debugging heading does too
- the schema skill says older data arriving later needs shouldReorder
  (shouldUpdate is for skipping work), with an eval
- Mutations and the debugging table say push only reaches lists whose
  args match, and non-filter args belong in nonFilterArgumentKeys

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
In a trigger eval (10 requests that should open a skill, 6 that shouldn't),
Sonnet opened a skill on 7/10 with these vs 3/10 with the keyword-list
descriptions, with no false alarms; Haiku 5/10 vs 3/10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
@ntucker
ntucker force-pushed the claude/project-thread-jnnzva branch from 5876d07 to ac37317 Compare October 21, 2026 02:01
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

…r state

The setup skill now writes its instructions to AGENTS.md, read by Cursor and
Codex, and links it from CLAUDE.md for Claude Code. The added lines say the app
manages server state with Data Client, so later agents use it and its skills
instead of hand-written fetching.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

…E.md

Setup now asks for an @AGENTS.md import only when a CLAUDE.md exists,
and the plugin diagram names AGENTS.md as where the rerun line goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
… warning

The bin already prints it when Windows denies symlinks, so the setup
skill doesn't need to repeat it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
Descriptions now name the broad situations each skill handles (any API
or async data, global shared state, data shapes, pagination, app-wide
side effects in place of useEffect), then the few API names agents are
most likely to see. Measured on a sample app with Sonnet, REST and
data-shape requests opened the right skill 5 of 6 times (was 3 of 6),
with no new false alarms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
…and schema descriptions

Requests the previous descriptions covered by name (moving the API to a
new base URL, a feed mixing several kinds of item, counts derived from
loaded data) stopped opening these skills after the rewrite. Naming
those situations brings them back without new false alarms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
…tead of listed cases

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
…nd vue debugging

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
Moves the push/unshift list-matching note from the react and vue skills to
the schema skill's nonFilterArgumentKeys section, points the debugging
table at the schema skill instead of naming its fixes, and drops Entity and
Collection from the rest description since the schema skill owns them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
…e steps to the agent

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
…ntKeys paragraph

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvySSBgX1ZiMWZ31xzFRLa
@ntucker

ntucker commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 1002a5d7d7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Merged via the queue into master with commit bb0dda6 Oct 21, 2026
30 checks passed
@ntucker
ntucker deleted the claude/project-thread-jnnzva branch October 21, 2026 03:15

This branch was successfully deployed

1 active deployment
docs-preview — 1002a5d7 Deployed Oct 21, 2026 by ntucker via docs deploy #3717
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants