Public: anyone can read this; the crew can change it.

Flaky: mcp-write-scope snapshot and the chrome click helper under load #9079

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Both flaky tests are fixed and pushed. I couldn't run the browser test here: Chromium exits at launch in this sandbox. `test/oauth-connect-browser.test.ts` failed on that ("chromium exited before printing its devtools address"), so the click retry and the longer timeouts have not run against a real browser. The forge's check will be their first real run.

shipped production is serving it deploy 8c46a8d

opened this

Both flaky tests are fixed and pushed. I couldn't run the browser test here: Chromium exits at launch in this sandbox. test/oauth-connect-browser.test.ts failed on that ("chromium exited before printing its devtools address"), so the click retry and the longer timeouts have not run against a real browser. The forge's check will be their first real run.

The new retry tests and test/mcp-write-scope.test.ts pass, and tsc --noEmit is clean. I did not run scripts/gate.ts, and I did not see whether the repository has one.

What changed

  • test/mcp-write-scope.test.ts: the table row-count snapshot now skips route_timing and usage_count, the two tables a timer flushes. A flush between the two reads can no longer make the snapshot differ. The event-log comparison is unchanged.
  • test/helpers/chrome.ts: clickAndFollow now runs each step through a new retryStep. It retries a step that throws, such as clicking an element the page hasn't drawn yet, every 50 ms until the deadline, then fails naming the last error.
  • test/chrome-retry.test.ts: new. One test fails 3 times, then succeeds, and checks the step ran 4 times. Another never succeeds and checks that it throws at the deadline. The first fails without the retry loop, and neither needs Chromium.
  • test/oauth-connect-browser.test.ts: the test timeouts go to 40 s and 60 s. The click helper's follow window goes from its 15 s default to 30 s.

I couldn't see the earlier attempt, commit b55f16c6: it isn't in this clone. I wrote the fix from the issue text alone.

I did not add a keep_plan entry on the issue; I have no such tool in this session.

To try it: bun test test/chrome-retry.test.ts test/mcp-write-scope.test.ts. test/oauth-connect-browser.test.ts needs a machine where Chromium launches.

Closes #9031

commented

Both flaky tests are fixed and pushed. I couldn't run the browser test here: Chromium exits at launch in this sandbox. test/oauth-connect-browser.test.ts failed on that ("chromium exited before printing its devtools address"), so the click retry and the longer timeouts have not run against a real browser. The forge's check will be their first real run.

The new retry tests and test/mcp-write-scope.test.ts pass, and tsc --noEmit is clean. I did not run scripts/gate.ts, and I did not see whether the repository has one.

What changed

  • test/mcp-write-scope.test.ts: the table row-count snapshot now skips route_timing and usage_count, the two tables a timer flushes. A flush between the two reads can no longer make the snapshot differ. The event-log comparison is unchanged.
  • test/helpers/chrome.ts: clickAndFollow now runs each step through a new retryStep. It retries a step that throws, such as clicking an element the page hasn't drawn yet, every 50 ms until the deadline, then fails naming the last error.
  • test/chrome-retry.test.ts: new. One test fails 3 times, then succeeds, and checks the step ran 4 times. Another never succeeds and checks that it throws at the deadline. The first fails without the retry loop, and neither needs Chromium.
  • test/oauth-connect-browser.test.ts: the test timeouts go to 40 s and 60 s. The click helper's follow window goes from its 15 s default to 30 s.

I couldn't see the earlier attempt, commit b55f16c6: it isn't in this clone. I wrote the fix from the issue text alone.

I did not add a keep_plan entry on the issue; I have no such tool in this session.

To try it: bun test test/chrome-retry.test.ts test/mcp-write-scope.test.ts. test/oauth-connect-browser.test.ts needs a machine where Chromium launches.

Changes requested — before the latest push

Two things to change. First: do not paper over the browser flake with 40s/60s timeouts. The direction is a time budget per test (#1242) and tests that never flake; a 60s test that passes only when given a minute hides the load problem. Keep the click retry (it fixes the real cause: clicking before the element is drawn) and drop the timeout bumps, or show from a run that the retry alone is not enough and say why. Second: the browser path was never run (Chromium did not launch for the builder), so the retry in clickAndFollow is unverified end to end; the preview/check run must show oauth-connect-browser passing before this lands. Also the write-scope snapshot now ignores route_timing and usage_count entirely, so a read-only token that wrote to those tables would go unnoticed; compare them as monotonic counters (not decreasing / not touched by a tool call) or assert the tool did not insert rows by key, rather than dropping them from the check. Security surface: none touched; the new retry tests are good and need no Chromium.

commented

I've made all three changes from the review and pushed them. The click retry still hasn't run in a real browser, because Chromium doesn't launch in my sandbox.

  • Timeouts: the 40 s and 60 s test timeouts and the 30 s follow window are gone. test/oauth-connect-browser.test.ts is back to its original budgets, and only the retryStep retry stays in clickAndFollow.
  • Write-scope snapshot: route_timing and usage_count are back in the no-write check.

- Rows for routes other than the test's own /mcp traffic are compared exactly, so a tool that wrote to those tables would now fail the test. - The /mcp rows can only grow: their summed n must not fall. - I matched the /mcp rows with pattern LIKE '%mcp%'. I didn't read the router, so the exact pattern string is a guess. - I did not add a test that a stray write to these tables fails the check.

  • Browser verification: the forge's check on this push is where oauth-connect-browser has to pass before this lands. If it fails there, the check log is the evidence for whether the retry alone is enough.

test/chrome-retry.test.ts and test/mcp-write-scope.test.ts pass, and tsc --noEmit is clean.

Changes requested — before the latest push

Test-only change, no product surface touched; the retryStep tests are good and need no Chromium. Two things to fix in one round:

  1. The new snapshot logic has no test that would fail without it. The point of keeping route_timing/usage_count in the check is that a stray tool write is caught, and nothing proves it. Add a test (on an extracted helper or a seeded row) showing that an inserted or changed non-/mcp row makes the snapshot differ, and that an /mcp row that grows does not.
  2. pattern LIKE %mcp% is a guess at the router pattern, and the ORDER BY 1,2,3,4 is a guess at the column count. Read the router and the two table schemas and match the real key (exact equality, not a substring) and order by the real primary key. Also say why the exact comparison of the other rows cannot itself flake when a timer flushes a different route between the two reads; if it can, compare those as non-decreasing too, with no rows removed.

The click retry is unverified in a real browser (the builder had no Chromium); the forge check must show oauth-connect-browser passing before it lands. Timeout bumps are gone, which is right.

commented

This fixes the two flaky tests from #9031. The MCP write-scope check now catches a stray write to a timer-flushed counter, and that is proved by a test. The chrome click helper retries until the element exists.

Write-scope snapshot

  • The snapshot moved out of test/mcp-write-scope.test.ts into test/helpers/write-snapshot.ts. It now takes the database as an argument.
  • The test's own MCP traffic is matched with pattern = '/mcp', which is exactly the pattern the router uses for that route (mcpRoutes, src/http/mcp.ts:361). The earlier substring match (LIKE '%mcp%') is gone.
  • Each counter table is ordered by its real primary key, taken from its migration: (hour, pattern, bucket) for route_timing and (hour, pattern, method, status, role, surface) for usage_count.
  • /mcp rows are left out of the snapshot and summed separately. That sum may rise but never fall. Every other table is still held to its row count.

test/write-snapshot.test.ts (new) uses the real migrations and the real router. It shows that:

  • the router answers POST /mcp with the pattern the helper matches, and each table's real primary key matches the helper's ordering;
  • adding, changing or deleting a counter row outside /mcp makes the snapshot differ;
  • a row whose pattern only contains "mcp" is checked exactly, so the old LIKE would have failed this;
  • /mcp rows growing, or a new hour's /mcp row appearing, leaves the snapshot equal and raises the /mcp sum by the exact amount;
  • a new row in any other table makes the snapshot differ.

Why the exact check on other rows can't flake. A counter row only exists for a request the server answered, filed under its route pattern. In this test the server only ever answers POST /mcp, so there is nothing waiting to be flushed under any other pattern. A new hour starting only adds an /mcp row, which the snapshot leaves out. The 30-day cleanup finds nothing in a database created minutes earlier. So I did not loosen the other rows to "may only grow"; that would let an inserted row through.

Chrome click retry. It is unchanged in this round. No Chromium was available here, so oauth-connect-browser has not been run in a real browser; the forge's check on this push has to show it passing before this lands.

Checks run here: bun scripts/gate.ts, bun x tsc --noEmit, and bun test test/write-snapshot.test.ts test/mcp-write-scope.test.ts test/chrome-retry.test.ts all pass (12 tests).

🤖 Generated with Claude Code

Approved

Both earlier asks are met. (1) The snapshot is now test/helpers/write-snapshot.ts, and test/write-snapshot.test.ts would fail without it: an inserted, changed or deleted non-/mcp counter row makes it differ, and /mcp growth or a new hour row does not. (2) The /mcp match is exact equality on the pattern, and a test pins it to what the router returns for POST /mcp. A second test pins the ORDER BY columns to each table real primary key via pragma_table_info. The header comment gives a sound argument for why the exact comparison cannot flake. The SQL is built only from sqlite_master and constant names, never user text. Test-only, no README or feature surface touched, no new dependency. Non-blocking: retryStep re-evaluates the click expression after a thrown exception, so a click that fires and then throws would fire twice; unlikely for these steps. The click retry has not run in a real browser, so the forge check must show oauth-connect-browser passing.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs