Bug
The Tasks sandbox MCP client sends the header x-posthog-read-only (with a hyphen between read and only), but the MCP server reads x-posthog-readonly (no second hyphen). The two names never match, so the server's read-only enforcement never applies to sandbox-spawned MCP sessions.
The server has a query-param fallback (?readonly=...) but the sandbox does not set it, so there is no second path that would catch the mismatch.
Code references
Sender — products/tasks/backend/temporal/process_task/utils.py:363
{\"name\": \"x-posthog-read-only\", \"value\": str(read_only).lower()},
Receiver — services/mcp/src/index.ts:333
const readOnlyRaw = request.headers.get('x-posthog-readonly') || url.searchParams.get('readonly')
The sender line was introduced on 2026-03-09 in 2a68f4dd7750, so the mismatch has been present for ~2 months.
Impact
Two-directional risk:
- Today: sandboxes spawned with MCP scopes that include any write capability still see those write tools, regardless of whether the caller intended a read-only session, because the read-only filter on the MCP side is silently bypassed.
- If the server is later fixed to also accept
x-posthog-read-only: any sandbox-launched workflow that relies on write tools (e.g. an agent harness that needs write-capable MCP tools) can suddenly lose them when the caller passes read_only-style scopes alongside the (now-honored) header.
Suggested fix
Align both ends on a single header name. Recommend standardizing on the server's x-posthog-readonly (no second hyphen) since the MCP server is also consumed by external clients (Claude Desktop, etc.), and changing the wire format on the receiver side has a wider blast radius than fixing one Python sender.
Concretely:
- Update
_build_mcp_config in products/tasks/backend/temporal/process_task/utils.py to emit x-posthog-readonly.
- Update related tests in
products/tasks/backend/temporal/process_task/tests/test_utils.py.
- Audit any other internal callers (e.g.
services/agentsh, eval harnesses) for the same name drift before landing.
- Add a lightweight integration test that exercises the wire format end-to-end so this kind of mismatch can't silently regress.
Notes
Spotted while reviewing the Signals agent dev branch — the agent uses the standard sandbox MCP plumbing, which is how the issue surfaced, but the bug itself is independent of that work and affects every sandbox MCP consumer.
Bug
The Tasks sandbox MCP client sends the header
x-posthog-read-only(with a hyphen betweenreadandonly), but the MCP server readsx-posthog-readonly(no second hyphen). The two names never match, so the server's read-only enforcement never applies to sandbox-spawned MCP sessions.The server has a query-param fallback (
?readonly=...) but the sandbox does not set it, so there is no second path that would catch the mismatch.Code references
Sender —
products/tasks/backend/temporal/process_task/utils.py:363{\"name\": \"x-posthog-read-only\", \"value\": str(read_only).lower()},Receiver —
services/mcp/src/index.ts:333The sender line was introduced on 2026-03-09 in
2a68f4dd7750, so the mismatch has been present for ~2 months.Impact
Two-directional risk:
x-posthog-read-only: any sandbox-launched workflow that relies on write tools (e.g. an agent harness that needs write-capable MCP tools) can suddenly lose them when the caller passesread_only-style scopes alongside the (now-honored) header.Suggested fix
Align both ends on a single header name. Recommend standardizing on the server's
x-posthog-readonly(no second hyphen) since the MCP server is also consumed by external clients (Claude Desktop, etc.), and changing the wire format on the receiver side has a wider blast radius than fixing one Python sender.Concretely:
_build_mcp_configinproducts/tasks/backend/temporal/process_task/utils.pyto emitx-posthog-readonly.products/tasks/backend/temporal/process_task/tests/test_utils.py.services/agentsh, eval harnesses) for the same name drift before landing.Notes
Spotted while reviewing the Signals agent dev branch — the agent uses the standard sandbox MCP plumbing, which is how the issue surfaced, but the bug itself is independent of that work and affects every sandbox MCP consumer.