DEVX-314: Hotfix Plaud Cloudflare 403 + remove stale .mcp.json (0.1.1) - #16
Merged
allenahner merged 7 commits intoMay 17, 2026
Merged
Conversation
Plaud's API sits behind Cloudflare bot protection. The self-identifying User-Agent `rootscribe/0.1.0 (+https://github.com/...)` started getting served a Cloudflare challenge page (HTTP 403) on 2026-05-15, breaking the sync poller. Token was valid; the request never reached Plaud. Confirmed via direct curl: bot-style UA → 403 Cloudflare HTML, browser UA → 200 JSON, curl-default UA → 200 JSON. The `name/version (+url)` shape is the trigger. This commit: * Swaps `USER_AGENT` in server/src/plaud/client.ts to a Chrome string. * Encodes the lesson as a regression test in client.test.ts that forbids any UA matching `^name/ver (+http...)`. * Removes the unused .mcp.json from the repo root — the inbox MCP is now provided via the seedkit plugin's plugin-level .mcp.json, so the workspace-root copy was stale. A proper Cloudflare-aware 403 branch + configurable UA env var is scoped under DEVX-314 for a follow-up PR. This commit is partial progress on that ticket: the hotfix unblocks the poller for the 0.1.0 release; the longer-lived fix lands later. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR hotfixes Plaud API requests by replacing the self-identifying User-Agent with a browser-style User-Agent to avoid Cloudflare 403 challenges, and removes a stale MCP workspace config.
Changes:
- Updated Plaud client User-Agent to a Chrome browser string.
- Adjusted the Plaud client test to reject bot-style User-Agent patterns.
- Removed the root
.mcp.jsonfile.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
server/src/plaud/client.ts |
Sets the Plaud fetch User-Agent to a browser-style value. |
server/src/plaud/client.test.ts |
Updates header regression coverage for the new User-Agent behavior. |
.mcp.json |
Removes stale MCP server configuration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
First proper changelog for RootScribe, covering everything since the fork from rsteckler/applaud@v0.5.6 (commit da7ae11). 168 commits across 7 DEVX tickets plus the foundational inbox-MCP work. Format follows Keep a Changelog 1.1 + SemVer. Grouped as Added / Changed / Fixed / Removed with sub-sections by DEVX ticket so each section is traceable to its ticket and PR history. The [0.1.0] reference link points at the GitHub release-tag URL, which will become live when v0.1.0 is pushed post-merge. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR #16 review feedback from Copilot: the previous merge order in plaudFetch spread `init.headers` AFTER the default user-agent, so a caller passing their own `user-agent` (including a bot-pattern UA) would override the safe default — and the regression test added in 44f6243 only exercised the no-headers default path, so it would not have caught such a caller. Move `"user-agent": USER_AGENT` to AFTER the `...init.headers` spread so the lock applies structurally regardless of caller input. Keep the other defaults (accept, authorization) overridable per existing behavior — only the UA needs to be locked. Add a second regression test that calls plaudFetch with a bot-pattern `user-agent` in `init.headers` and asserts the outgoing UA does NOT match the bot pattern and does NOT contain "rootscribe/". No current caller passes a UA in init.headers (verified by grep across server/src/plaud/* and server/src/routes/auth.ts), so this is a forward-looking guard rather than a behavior change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The package version `0.1.0` was already baked into the broken self-identifying UA (`rootscribe/0.1.0 (+url)`), so 0.1.0 is the unreleased pre-Cloudflare baseline. This hotfix is the patch release on top, per SemVer 2.0.0: - Bump version from 0.1.0 to 0.1.1 across all five package.json files (root, server, web, inbox-mcp, shared). - Split CHANGELOG.md: [0.1.1] section gets the Cloudflare hotfix + structural UA lock + .mcp.json removal + CHANGELOG.md itself. [0.1.0] section becomes the unreleased baseline documenting the 135 commits across 6 DEVX tickets (DEVX-96/99/100/101/102/103) since the fork from rsteckler/applaud@v0.5.6. - Update CHANGELOG reference links to point [0.1.1] at the tag URL and [0.1.0] at a fork-point→tag compare URL. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR #16 round-2 review feedback from Copilot: HTTP header names are case-insensitive (RFC 7230 §3.2) but JS object keys are case-sensitive strings. The previous "spread first, then lock" merge order left both the caller's casing variant (e.g. "User-Agent") AND the locked "user-agent" in the merged headers object — and fetch's Headers init concatenates same-named entries, so the outgoing request would carry the bot-pattern UA combined with the locked value (e.g. "rootscribe/0.1.0 (+url), Mozilla/..."). Fix: strip ALL case variants of "user-agent" from `init.headers` BEFORE the spread, then add the locked lowercase key. The lock is now structural across every possible caller casing. Add a third regression test that passes both "User-Agent" and "USER-AGENT" with bot-pattern values and asserts: 1. Only the locked lowercase "user-agent" key remains in the outgoing headers (no case-variant leaks). 2. No header value anywhere in the merged object contains the "rootscribe/" or `name/ver (+http...)` pattern. The new test plus the existing two regressions now cover three attack paths: default-headers, lowercase-override, and case-variant override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR #16 round-3 review feedback from Copilot: bumping the five package.json files to 0.1.1 was incomplete — three runtime constants plus one README doc string still hard-coded "0.1.0": - inbox-mcp/src/index.ts:30 — MCP server `version` metadata exposed to clients (now "0.1.1") - server/src/webhook/post.ts:102 and :184 — outbound webhook User-Agent header on both prod and test paths (now "rootscribe/0.1.1") - README.md:112 — documented webhook UA (now "rootscribe/0.1.1") Without these bumps, 0.1.1 would have shipped advertising itself as 0.1.0 on every webhook, MCP handshake, and in the README. The structurally correct fix is to derive these values from package.json at runtime/build time — that's scoped under DEVX-314 follow-up. A comment in inbox-mcp/src/index.ts marks the source-of-truth invariant. Also fix the CHANGELOG.md `[0.1.0]` reference link Copilot flagged: it previously pointed at `da7ae11...v0.1.1` which INCLUDED the hotfix commits, contradicting the section text. Since 0.1.0 was never tagged, the cleanest fix is to drop the link entirely and leave HTML comments explaining why. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR #16 round-4 review feedback from Copilot: the CHANGELOG [0.1.1] section said "Two regression tests ... default-headers path AND caller-override path" but the PR actually added three tests — the case-variant override test landed in e9abdbe and was not reflected in the release notes I wrote earlier. Update the bullet to list all three covered paths explicitly: default-headers, lowercase caller-override, and case-variant caller-override (User-Agent / USER-AGENT). Keeps the release notes and test file in sync within the same PR that added the coverage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First tagged release of RootScribe (
v0.1.1). De facto0.1.0was the unreleased baseline that the broken UA was identifying as; this hotfix is the patch release on top.USER_AGENTinserver/src/plaud/client.tsto a Chrome string. The previous self-identifying UA (rootscribe/0.1.0 (+https://github.com/...)) started getting Cloudflare-challenged with a 403 on 2026-05-15, breaking the sync loop. Token was valid; Cloudflare blocked the request at the edge.user-agentheader is now applied AFTER theinit.headersspread so callers cannot override it back to a bot-pattern UA (addresses Copilot review feedback on the first revision).client.test.tsnow forbids any UA matching^name/ver (+http...)on both the default-headers path AND the caller-override path. Intentionally stricter than pinning the Chrome string so DEVX-314's follow-up env-var work doesn't break it..mcp.jsonwas stale — the inbox MCP is now provided via the seedkit plugin's plugin-level.mcp.json. Removal is intentional.rsteckler/applaud@v0.5.6) and this 0.1.1 hotfix. Format follows Keep a Changelog 1.1 + SemVer.package.jsonfiles bumped from0.1.0to0.1.1.Background
Direct curl diagnostic on 2026-05-15:
rootscribe/0.1.0 (+url)curl/8.xdefaultThe proper fix — Cloudflare-aware 403 error class (
PlaudEdgeBlockedError) +ROOTSCRIBE_PLAUD_USER_AGENTenv var — is scoped under DEVX-314 for a follow-up PR. This one is the hotfix + structural lock + changelog + version bump needed to shipv0.1.1.Test plan
pnpm lint— cleanpnpm typecheck— clean across all 4 workspacespnpm test:coverage— 776+ unit tests pass; coverage above pinned thresholds (95.36% statements, 86.59% branches)pnpm -w test -- --run plaud— 79/79 plaud tests pass, including the new caller-override regressionapi.plaud.ai/file/simple/webwith the new Chrome UA returns 200 JSONNotes
allenahner/DEVX-314-....gh pr create --repo Root-Functional-Medicine/rootscribeexplicitly to avoid the knownghupstream-fork bug (see CLAUDE.md → "Critical PR rule")..githooks/pre-push) verified origin push targetsRoot-Functional-Medicine/rootscribe, notrsteckler/applaud.🤖 Generated with Claude Code