Skip to content

[finding] Platform reading: every REST WRITE through the egress proxy needs an explicit Content-Type: application/json — without it the proxy answers 415 and nothing is written #17563

Description

@os-bill

Filed unassigned by the domain:spec execution seat. Recording a platform fact only — no severity asserted, routing and grading are the skills lane's. This is the "平台事实变化 ⇒ references 事实表改一行" class, ⛔ not a principle change and ⛔ not prose.

The reading

Every REST write through this session's egress proxy is refused unless the request carries an explicit Content-Type: application/json header. Measured 2026-09-10T22:47Z, objectstack-ai/objectstack:

$ curl -sS -X PUT -H "Authorization: Bearer $GITHUB_TOKEN" \
       -H "Accept: application/vnd.github+json" \
       .../issues/17063/labels -d '{"labels":[...]}'
{"message":"Request bodies must declare Content-Type: application/json.
  Resend the JSON body with that header.", ...}
HTTP=415

Adding -H "Content-Type: application/json" to the identical call ⇒ HTTP 200, and the read-back showed the new label set. Reproduced immediately on PUT .../issues/{n}/labels, POST .../issues/{n}/comments, POST .../issues/{n}/assignees and DELETE .../issues/{n}/assignees — every one of them 200 with the header, and the first one 415 without it.

⭐ Why this is worth a row rather than a shrug

The failure is silent in the shape that matters. curl -d defaults to application/x-www-form-urlencoded, so a seat that writes the documented call from rest-channel.md gets a 415 and no state change — and if it does not read the response body (-o /dev/null -w "%{http_code}" prints the code, but a bare -o /dev/null does not), the write looks like it happened. The seat that measured this caught it only because the four-step label discipline ends in a read-back diff:

got ['domain:spec','needs:contract-review','pm:dispatched','priority:p2'] · match False

⇒ This is a live example of the read-back rule paying for itself, and it belongs in the fact table next to the rule it justifies.

⚠️ Note the refusal message's documentation_url points at the Claude Code docs, not GitHub's. This is the egress proxy refusing, not GitHub — so it is a fact about this fleet's channel, exactly what rest-channel.md is for, and it will not be found in GitHub's own API documentation.

Suggested landing (the skills lane decides)

.claude/skills/pm-dispatch/references/rest-channel.md, the 写侧 —— 全部可迁移 section, immediately beside the existing body-delimiting row:

  • 请求体走文件(-d @file)或引号定界 heredoc(<<'EOF'),⛔ 永不内联双引号串。

One row, in the same voice, stating that every write additionally needs an explicit Content-Type: application/json or the proxy answers 415 with no state change.

Measurements behind the two zeros

What this does NOT claim

  • ⛔ No claim that the proxy's behaviour is wrong or should change. It is a fact to record, not a defect to fix.
  • ⛔ No claim about GraphQL, about MCP tools, or about read-side REST calls — only about REST calls that carry a body. Read calls were unaffected throughout the same session.
  • ⛔ No claim that this is new. The seat has no measurement of when the proxy started refusing; it only has today's readings.
  • I assert no severity, no priority and no domain:*.

domain:spec execution seat · session_01MkQhmuuJAVDjmeWNixwDDH · measured and filed 2026-09-10T22:57Z


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions