Skip to content

fix(github-copilot): bound model discovery and embeddings JSON response#96499

Merged
sallyom merged 2 commits into
openclaw:mainfrom
hugenshen:fix/bound-copilot-json-responses
Jun 25, 2026
Merged

fix(github-copilot): bound model discovery and embeddings JSON response#96499
sallyom merged 2 commits into
openclaw:mainfrom
hugenshen:fix/bound-copilot-json-responses

Conversation

@hugenshen

Copy link
Copy Markdown
Contributor

What Problem This Solves

The GitHub Copilot embeddings plugin already bounds its error response
bodies via readResponseTextLimited (8 KiB cap), but the success JSON reads
for both model discovery and the embeddings call used an unbounded
await response.json(). This asymmetry means a misbehaving or hostile Copilot
API endpoint can stream an arbitrarily large success body into memory before
parsing, causing memory pressure on the model discovery and embeddings hot path —
while the same file already demonstrates awareness that response bodies need to
be capped.

The error body is bounded on both paths; this PR closes the symmetric gap on
the success-JSON side, matching the pattern already applied to other provider
paths in the #95103 / #95108 / #95218 response-limit campaign.

Changes

  • discoverEmbeddingModels: replaces the try { payload = await response.json() } catch
    block with readProviderJsonResponse(response, "github-copilot.model-discovery")
    (16 MiB cap) from openclaw/plugin-sdk/provider-http.
  • embed (embeddings call): replaces the identical pattern with
    readProviderJsonResponse(response, "github-copilot.embeddings").
  • isCopilotSetupError: adds the "github-copilot.model-discovery" label prefix
    so auto-selection still falls through on malformed discovery responses with the
    new error format.
  • embeddings.test.ts:
    • Updates mockDiscoveryResponse to return real Response objects (required by
      the streaming bounded reader; plain { json: async () => ... } objects lack
      .body).
    • Updates the malformed-JSON fixture to use a non-JSON body string instead of
      a throwing .json() stub.
    • Updates the shouldContinueAutoSelection assertion to match the new error
      message prefix "github-copilot.model-discovery: malformed JSON response".

Real behavior proof

  • Behavior addressed: An untrusted Copilot API success response with no
    Content-Length and an oversized/never-ending body must not be buffered whole;
    the read must stop at the cap, cancel the stream, and throw a bounded error —
    while valid small responses still parse unchanged.
  • Real environment tested: A real node:http server (createServer) bound
    to 127.0.0.1, streaming a JSON body in 64 KiB chunks with no
    Content-Length
    header, driving the real exported readResponseWithLimit
    helper (the same helper readProviderJsonResponse wraps internally) on
    Node v22.22.0.
  • Exact steps:
    1. Boot the server; GET /huge streams an unbounded JSON object (~24 MiB) with
      no Content-Length; GET /small returns one valid response.
    2. Drive readResponseWithLimit(response, 1 MiB) against /huge and assert
      it rejects + the server socket is closed early.
    3. Negative control: run the OLD path await response.json() against the
      same /huge body and measure how many bytes the server pushed.
    4. Drive readResponseWithLimit(response, 1 MiB) against /small and assert
      the result is parsed intact.
  • Evidence after fix:
    • Bounded case: threw JSON response exceeds 1048576 bytes; server socket
      closed early (stream cancelled); only 1,064,178 bytes reached the
      wire — near the 1 MiB cap, not the 24 MiB body.
    • Negative control: the unbounded response.json() pulled the full
      25,165,824 bytes
      (>23x the bounded read), proving the cap is load-bearing.
    • Small case: parsed intact, no truncation.
  • Observed result: ALL PROOF ASSERTIONS PASSED. Plus the in-repo Vitest
    suite passes 1/1 file, 12/12 tests — including the existing bounds-error
    body tests and the updated malformed-JSON fixture. oxlint is clean on
    changed files.
  • What was not tested: Live Copilot API calls with an oversized response.
    The proof instead measures bytes-on-wire and early socket close, which is
    the load-bearing signal.

Evidence

[case 1] oversized JSON, cap=1024 KiB, NO content-length
  ok: readResponseWithLimit rejected on oversized body — true
  ok: bounded error message present (got: JSON response exceeds 1048576 bytes) — true
  ok: bytes on wire stayed near the cap, not the full body (sent 1064178) — true

[negative control] OLD unbounded `await response.json()` buffers whole body
  ok: unbounded read pulled the FULL ~24 MiB body (sent 25165824), proving the cap is load-bearing — true

[case 3] normal small JSON still parses unchanged
  ok: small body parsed into result — true
  ok: result content intact (valid small body not truncated) — true

ALL PROOF ASSERTIONS PASSED
 Test Files  1 passed (1)
      Tests  12 passed (12)
   Start at  00:37:43
   Duration  4.01s

Label: security

AI-assisted.

…se reads

The GitHub Copilot embeddings plugin already bounds its error response
bodies via readResponseTextLimited, but the success JSON reads for both
model discovery and the embeddings call used unbounded response.json().
Route both through readProviderJsonResponse (16 MiB cap).

Update isCopilotSetupError to recognise the new error label prefix so
auto-selection still falls through on malformed discovery responses.
Update tests to use proper Response objects and the new error messages.

AI-assisted.

Co-authored-by: Cursor <[email protected]>
@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 25, 2026, 2:38 PM ET / 18:38 UTC.

Summary
The PR changes GitHub Copilot model discovery and memory embedding success JSON parsing from unbounded response.json() to bounded readProviderJsonResponse, with setup-error matching and test fixture updates.

PR surface: Source -3, Tests -1. Total -4 across 2 files.

Reproducibility: yes. Current main still calls response.json() on both Copilot success paths, and the PR body includes terminal proof showing the old path reads an oversized stream while the bounded reader cancels near the cap.

Review metrics: 1 noteworthy metric.

  • Success JSON caps: 2 added: 16 MiB discovery, 64 MiB embeddings. These are new fail-closed limits on success responses, so maintainers should notice the compatibility boundary before merge.

Stored data model
Persistent data-model change detected: serialized state: extensions/github-copilot/embeddings.test.ts, unknown-data-model-change: extensions/github-copilot/embeddings.test.ts, unknown-data-model-change: extensions/github-copilot/embeddings.ts, vector/embedding metadata: extensions/github-copilot/embeddings.test.ts, vector/embedding metadata: extensions/github-copilot/embeddings.ts. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] Merging intentionally adds fail-closed success JSON caps: Copilot model discovery now rejects responses over 16 MiB and Copilot embeddings reject responses over 64 MiB, while current main accepted unbounded successful bodies.

Maintainer options:

  1. Accept the bounded Copilot caps (recommended)
    Proceed if maintainers are comfortable that 16 MiB is enough for model discovery and 64 MiB preserves the established memory embedding response budget.
  2. Tune the caps before merge
    If Copilot discovery or embedding responses are expected to exceed these budgets, adjust the cap or split behavior before landing.

Next step before merge

  • No automated repair is needed; the remaining action is maintainer review of the intentional bounded-response compatibility boundary and normal CI readiness.

Security
Cleared: The diff hardens successful response parsing without adding dependencies, workflow changes, secret handling, package-resolution changes, or new code-execution paths.

Review details

Best possible solution:

Land the focused bounded-reader change after CI and maintainer acceptance of the 16 MiB discovery cap and 64 MiB embeddings cap.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main still calls response.json() on both Copilot success paths, and the PR body includes terminal proof showing the old path reads an oversized stream while the bounded reader cancels near the cap.

Is this the best way to solve the issue?

Yes. After the follow-up commit, the PR uses the shared bounded JSON helper, keeps model discovery on the generic provider cap, and preserves the 64 MiB memory embedding response budget for /embeddings.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 66e2fcc6f83e.

Label changes

Label changes:

  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes terminal proof from a real local node:http stream with a negative control, early cancellation evidence, small-response success, and targeted test output; the follow-up comment reports post-edit validation after preserving the embeddings cap.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: This is focused provider hardening with limited blast radius, but it can affect GitHub Copilot memory embedding users at the new response-size boundaries.
  • merge-risk: 🚨 compatibility: The PR changes previously unbounded successful Copilot responses into capped failures for over-budget model discovery or embedding responses.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes terminal proof from a real local node:http stream with a negative control, early cancellation evidence, small-response success, and targeted test output; the follow-up comment reports post-edit validation after preserving the embeddings cap.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes terminal proof from a real local node:http stream with a negative control, early cancellation evidence, small-response success, and targeted test output; the follow-up comment reports post-edit validation after preserving the embeddings cap.
Evidence reviewed

PR surface:

Source -3, Tests -1. Total -4 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 10 13 -3
Tests 1 14 15 -1
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 24 28 -4

What I checked:

Likely related people:

  • feiskyer: Authored the merged GitHub Copilot memory-search embedding provider that added extensions/github-copilot/embeddings.ts and the Copilot /models and /embeddings paths. (role: introduced behavior; confidence: high; commits: 88d3620a85bf; files: extensions/github-copilot/embeddings.ts)
  • vincentkoc: Recently hardened Copilot embedding error bodies and added the memory remote JSON response limit work that defines the sibling 64 MiB budget. (role: recent area contributor; confidence: high; commits: 4d37f42df7f2, 4ad875308f5c, be7d86ed8005; files: extensions/github-copilot/embeddings.ts, packages/memory-host-sdk/src/host/response-snippet.ts)
  • Alix-007: Authored the merged provider JSON response cap PR that introduced the shared helper this PR applies to the Copilot plugin. (role: shared helper contributor; confidence: high; commits: 2592f8a51a4e; files: src/agents/provider-http-errors.ts, src/agents/provider-http-errors.test.ts)
  • sallyom: Pushed the follow-up commit on this PR that preserved the 64 MiB Copilot embeddings success-response budget after the prior ClawSweeper finding. (role: recent PR follow-up owner; confidence: medium; commits: bf59962edbb7; files: extensions/github-copilot/embeddings.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 24, 2026
@sallyom sallyom self-assigned this Jun 25, 2026
@sallyom

sallyom commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Maintainer edit pushed: bf59962edbb keeps Copilot model discovery on the shared 16 MiB provider JSON cap, and raises the Copilot embeddings success JSON cap to 64 MiB to match the sibling remote memory embedding response budget.

The existing bounded-reader proof remains sufficient for review: it verifies that the shared reader enforces the configured byte budget and cancels the stream on overflow. This edit changes only the embeddings maxBytes value to the sibling 64 MiB budget; it does not change the reader mechanics.

Focused proof after the edit: node scripts/run-vitest.mjs extensions/github-copilot/embeddings.test.ts, git diff --check origin/main...HEAD, and local autoreview clean.

@sallyom

sallyom commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Maintainer local review: merge-ready once required CI is green.

Local autoreview is clean on the updated branch, focused Copilot embeddings tests passed, and the maintainer cap edit aligns embeddings success JSON with the sibling 64 MiB remote memory embedding response budget while keeping model discovery at 16 MiB. No remaining compatibility/API/config concerns from my review.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 25, 2026
@sallyom
sallyom merged commit 1aa7caf into openclaw:main Jun 25, 2026
113 of 121 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 26, 2026
…se (openclaw#96499)

* fix(github-copilot): bound model discovery and embeddings JSON response reads

The GitHub Copilot embeddings plugin already bounds its error response
bodies via readResponseTextLimited, but the success JSON reads for both
model discovery and the embeddings call used unbounded response.json().
Route both through readProviderJsonResponse (16 MiB cap).

Update isCopilotSetupError to recognise the new error label prefix so
auto-selection still falls through on malformed discovery responses.
Update tests to use proper Response objects and the new error messages.

AI-assisted.

Co-authored-by: Cursor <[email protected]>

* fix(github-copilot): use memory embedding response cap

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: sallyom <[email protected]>
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…se (openclaw#96499)

* fix(github-copilot): bound model discovery and embeddings JSON response reads

The GitHub Copilot embeddings plugin already bounds its error response
bodies via readResponseTextLimited, but the success JSON reads for both
model discovery and the embeddings call used unbounded response.json().
Route both through readProviderJsonResponse (16 MiB cap).

Update isCopilotSetupError to recognise the new error label prefix so
auto-selection still falls through on malformed discovery responses.
Update tests to use proper Response objects and the new error messages.

AI-assisted.

Co-authored-by: Cursor <[email protected]>

* fix(github-copilot): use memory embedding response cap

Signed-off-by: sallyom <[email protected]>

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: sallyom <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: github-copilot merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants