Skip to content

fix(ws): derive attachment caps from the ws frame limit (#347) - #380

Merged
omridevk merged 2 commits into
mainfrom
fix/347-ws-payload-cap
Aug 10, 2026
Merged

fix(ws): derive attachment caps from the ws frame limit (#347)#380
omridevk merged 2 commits into
mainfrom
fix/347-ws-payload-cap

Conversation

@omridevk

@omridevk omridevk commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Closes #347.

Problem

@conciv/serve closes the ws at 16MiB (maxPayload, close code 1009). Since #349 every rpc procedure of a tab rides one shared /rpc-ws socket, so a single oversized frame kills the whole tab's rpc.

The composer image adapter capped raw files at 20MB — but base64 inflates ×4/3, so a 20MB image becomes a ~28MB frame. Any image between ~11.6MB and 20MB passed the adapter check and 1009-poisoned the socket. The server-side zod cap (MAX_ATTACHMENT_BASE64_LENGTH = 27_962_028) agreed with the 20MB adapter cap, not with the wire limit — and never runs anyway, because ws kills the frame before orpc parses it.

Bonus edge: chat.send allows 16 content parts; 16 × 1MB text parts alone exactly reach the frame limit before the JSON envelope, so per-attachment caps alone can't prevent the poison.

Fix — one source, everything derives

  • @conciv/protocol/rpc-types: WS_MAX_PAYLOAD_BYTES (16MiB), WS_PAYLOAD_MARGIN_BYTES (512KiB), WS_RPC_PAYLOAD_BUDGET_BYTES. (The constant moved to protocol rather than being imported from @conciv/serve as the issue sketched, because serve imports ws/node:events at module top and browser bundles can't take that.)
  • @conciv/serve imports the limit from protocol (new workspace dep) and no longer exports DEFAULT_MAX_PAYLOAD_BYTES.
  • @conciv/protocol/chat-types: MAX_ATTACHMENT_RAW_BYTES = MiB-floored ¾ of budget = 11MiB; the base64 schema cap derives from it.
  • ui-kit-chat image adapter derives its cap + error copy ("exceeds the 11MB image limit") from the protocol constant. An at-cap image base64s to ~15.4MB, under the 16.25MB budget.
  • Widget checkSend gains an aggregate guard: serialized content over budget → composer warning ("Message too large to send…"), nothing hits the socket. Covers multi-part sums the per-attachment cap can't.
  • Recorder flush IT now asserts against WS_RPC_PAYLOAD_BUDGET_BYTES directly (its local margin constant was a duplicate).

Audit of other large-input procedures

  • Whiteboard elements.upsert takes an unbounded rows array — same bug class, needs recorder-flusher-style chunking. Left for a follow-up PR: the whiteboard suite is CI-only, so a chunking change can't be behavior-verified in this one.
  • Grab payloads and drafts are text-only and small. Clean.

Testing

  • New: adapter boundary tests (at-cap accepted, one-over rejected, base64-of-cap < budget invariant); send-guard tests (over-budget rejected with visible message, budget-sized attachment passes).
  • Green: typecheck + tests for protocol, serve, ui-kit-chat, app, extension-recorder (incl. the live-socket flush IT), fallow audit 0 introduced.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added shared size limits for WebSocket messages and attachments.
    • Attachment limits now align with the available message payload budget.
  • Bug Fixes

    • Oversized messages are rejected with a clear warning before sending.
    • Images exceeding the permitted size are rejected, while files at the limit remain supported.
    • WebSocket payload handling now consistently enforces the maximum size.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 39a14393-4664-4585-89bc-d7997c9b824d

📥 Commits

Reviewing files that changed from the base of the PR and between 75a391e and 197419f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/protocol/test/chat-types.test.ts
  • packages/ui-kit-chat/test/attachment-adapter.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui-kit-chat/test/attachment-adapter.test.ts

📝 Walkthrough

Walkthrough

The change centralizes WebSocket payload limits in the protocol package. The server, message validation, attachment validation, and recorder tests now use these shared limits. New tests cover oversized messages and attachment size boundaries.

Changes

WebSocket payload budget

Layer / File(s) Summary
Shared budget and server wiring
packages/protocol/src/rpc-types.ts, packages/protocol/src/chat-types.ts, packages/serve/...
The protocol exports WebSocket payload constants and derives attachment limits from the RPC budget. The WebSocket server uses the shared maximum payload value.
Attachment and message validation
packages/ui-kit-chat/src/primitives/attachment/attachment-adapter.ts, apps/conciv/src/pane/send-checks.ts
Attachment validation uses the shared raw-byte limit. Send validation rejects serialized messages above the RPC payload budget.
Boundary and frame-size tests
packages/ui-kit-chat/test/attachment-adapter.test.ts, packages/protocol/test/chat-types.test.ts, apps/conciv/test/send-checks.test.ts, packages/extensions/recorder/test/flush-socket.it.test.ts
Tests cover attachment boundaries, Base64 limits, oversized messages, and outbound frames near the shared RPC budget.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • conciv-dev/conciv#347 — The PR introduces shared WebSocket payload limits and applies them to message and attachment validation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes deriving attachment caps from the shared WebSocket frame limit, which is the main change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/347-ws-payload-cap

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns attachment and message limits with the shared WebSocket frame budget to prevent oversized RPC frames from closing a tab’s shared socket.

Changes:

  • Centralizes WebSocket payload limits in protocol.
  • Derives attachment caps and adds aggregate send-size validation.
  • Adds boundary and recorder frame-budget tests.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds the serve-to-protocol dependency and updates resolutions.
packages/ui-kit-chat/test/attachment-adapter.test.ts Tests image-size boundaries.
packages/ui-kit-chat/src/primitives/attachment/attachment-adapter.ts Derives the image cap from protocol.
packages/serve/src/serve.ts Uses the shared WebSocket limit.
packages/serve/package.json Adds the protocol dependency.
packages/protocol/src/rpc-types.ts Defines shared payload limits and budget.
packages/protocol/src/chat-types.ts Derives raw and base64 attachment caps.
packages/extensions/recorder/test/flush-socket.it.test.ts Uses the shared frame budget in assertions.
apps/conciv/test/send-checks.test.ts Tests aggregate payload rejection.
apps/conciv/src/pane/send-checks.ts Blocks messages exceeding the RPC budget.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

const MAX_ATTACHMENT_BASE64_LENGTH = 27_962_028
const MEBIBYTE = 1024 * 1024
export const MAX_ATTACHMENT_RAW_BYTES = Math.floor((WS_RPC_PAYLOAD_BUDGET_BYTES * 3) / 4 / MEBIBYTE) * MEBIBYTE
const MAX_ATTACHMENT_BASE64_LENGTH = Math.ceil(MAX_ATTACHMENT_RAW_BYTES / 3) * 4
Comment thread pnpm-lock.yaml Outdated
'@tanstack/react-router':
specifier: latest
version: 1.170.22(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
version: 1.170.23(react-dom@19.2.7(react@19.2.7))(react@19.2.7)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/ui-kit-chat/test/attachment-adapter.test.ts (2)

52-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the oversized fixture from MAX_ATTACHMENT_RAW_BYTES.

Line 52 hardcodes 12 MiB. The fixture is oversized only with the current shared constants. Use MAX_ATTACHMENT_RAW_BYTES + 1 so the test remains tied to the attachment contract.

Proposed fixture update
-    const big = new File([new Uint8Array(12 * 1024 * 1024)], 'huge.png', {type: 'image/png'})
+    const big = new File([new Uint8Array(MAX_ATTACHMENT_RAW_BYTES + 1)], 'huge.png', {type: 'image/png'})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui-kit-chat/test/attachment-adapter.test.ts` around lines 52 - 57,
Update the oversized file fixture in the adapter test to allocate
MAX_ATTACHMENT_RAW_BYTES + 1 bytes instead of the hardcoded 12 MiB value,
preserving the existing incomplete status and send rejection assertions.

59-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise the actual encoded payload at the cap.

Lines 59-72 check the add status and recompute the Base64 formula. They do not inspect the value produced by fileToDataSource. Call adapter.send(pending) or fileToDataSource(largest) and assert the returned data-source value length.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui-kit-chat/test/attachment-adapter.test.ts` around lines 59 - 72,
Update the cap test around createSimpleImageAttachmentAdapter to exercise the
actual encoded payload by calling adapter.send(pending) or
fileToDataSource(largest), then assert the returned data-source value length is
below WS_RPC_PAYLOAD_BUDGET_BYTES. Remove the standalone largestBase64Length
formula assertion while preserving the existing add-status checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/conciv/src/pane/send-checks.ts`:
- Around line 22-27: The checkSend payload-size validation currently measures
only content; update it to serialize and measure the complete outbound ORPC
frame containing sessionId, runId, and content, reusing the serializer used by
chatConnection where available. Keep the existing rejection response and
WS_RPC_PAYLOAD_BUDGET_BYTES threshold unchanged.

---

Nitpick comments:
In `@packages/ui-kit-chat/test/attachment-adapter.test.ts`:
- Around line 52-57: Update the oversized file fixture in the adapter test to
allocate MAX_ATTACHMENT_RAW_BYTES + 1 bytes instead of the hardcoded 12 MiB
value, preserving the existing incomplete status and send rejection assertions.
- Around line 59-72: Update the cap test around
createSimpleImageAttachmentAdapter to exercise the actual encoded payload by
calling adapter.send(pending) or fileToDataSource(largest), then assert the
returned data-source value length is below WS_RPC_PAYLOAD_BUDGET_BYTES. Remove
the standalone largestBase64Length formula assertion while preserving the
existing add-status checks.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c6efd31-b21a-48d1-a6ba-8c0e5c970672

📥 Commits

Reviewing files that changed from the base of the PR and between fb618ea and 75a391e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • apps/conciv/src/pane/send-checks.ts
  • apps/conciv/test/send-checks.test.ts
  • packages/extensions/recorder/test/flush-socket.it.test.ts
  • packages/protocol/src/chat-types.ts
  • packages/protocol/src/rpc-types.ts
  • packages/serve/package.json
  • packages/serve/src/serve.ts
  • packages/ui-kit-chat/src/primitives/attachment/attachment-adapter.ts
  • packages/ui-kit-chat/test/attachment-adapter.test.ts

Comment on lines +22 to +27
if (new TextEncoder().encode(JSON.stringify(content)).length > WS_RPC_PAYLOAD_BUDGET_BYTES)
return {
ok: false,
message: 'Message too large to send. Remove an attachment or shorten it.',
tone: 'warn',
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 10 '\bcheckSend\s*\(' apps/conciv packages --glob '*.{ts,tsx}'
rg -n -C 12 'JSON\.stringify|socket\.send|\.send\s*\(|WebSocket|rpc-ws' apps/conciv/src packages --glob '*.{ts,tsx}'

Repository: conciv-dev/conciv

Length of output: 50374


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- skill files ---'
find .. -type f \( -iname '*skill*' -o -iname 'AGENTS.md' -o -iname 'CLAUDE.md' \) -print 2>/dev/null | head -200

printf '%s\n' '--- target file ---'
sed -n '1,180p' apps/conciv/src/pane/send-checks.ts

printf '%s\n' '--- direct call sites ---'
rg -n -C 12 '\b(checkSend|WS_RPC_PAYLOAD_BUDGET_BYTES)\b' apps/conciv/src packages --glob '*.{ts,tsx,js,jsx}' --glob '!**/*.test.*'

Repository: conciv-dev/conciv

Length of output: 37444


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- repository instructions ---'
cat ../git/AGENTS.md
cat ../git/CLAUDE.md
cat ../git/.github/skills/code-review/SKILL.md
cat ../git/.claude/skills/conciv-review/SKILL.md

printf '%s\n' '--- chat send path ---'
rg -n -C 18 '\b(sendMessage|create.*Chat|make.*Chat|RPCLink|createORPCClient|chat\.send|chatSend)\b' ../git/apps/conciv/src ../git/packages/client ../git/packages/protocol --glob '*.{ts,tsx,js,jsx}'

printf '%s\n' '--- RPC transport definitions ---'
rg -n -C 20 'rpc-ws|RPCLink|WS_RPC_PAYLOAD_BUDGET_BYTES|WebSocket|socket\.send|JSON\.stringify' ../git/packages ../git/apps/conciv/src --glob '*.{ts,tsx,js,jsx}' --glob '!**/*.test.*'

Repository: conciv-dev/conciv

Length of output: 50374


🏁 Script executed:

set -euo pipefail

cd ../git

printf '%s\n' '--- root instructions and review skill ---'
sed -n '1,220p' AGENTS.md
sed -n '1,220p' CLAUDE.md
sed -n '1,220p' .claude/skills/conciv-review/SKILL.md

printf '%s\n' '--- relevant source files ---'
rg -l --glob '!**/*.test.*' --glob '!**/*.stories.*' --glob '!**/node_modules/**' \
  'sendMessage|createChat|makeChat|RPCLink|chat\.send|WS_RPC_PAYLOAD_BUDGET_BYTES' \
  apps/conciv/src packages/client packages/core packages/contract packages/protocol

printf '%s\n' '--- chat pane and client setup ---'
sed -n '240,285p' apps/conciv/src/pane/chat-pane.tsx
rg -n -C 16 --glob '!**/*.test.*' --glob '!**/*.stories.*' \
  'sendMessage|createChat|makeChat|connectionStatus' apps/conciv/src packages/client

printf '%s\n' '--- RPC client and router definitions ---'
rg -n -C 14 --glob '!**/*.test.*' --glob '!**/*.stories.*' \
  'RPCLink|createORPCClient|chat:|send:' packages apps/conciv/src \
  | grep -v '/node_modules/' | head -500

Repository: conciv-dev/conciv

Length of output: 50377


🏁 Script executed:

set -euo pipefail

cd ../git

printf '%s\n' '--- chat connection ---'
sed -n '1,260p' packages/client/src/chat-connection.ts

printf '%s\n' '--- browser transport ---'
sed -n '1,240p' packages/contract/src/browser-transport.ts
sed -n '1,180p' packages/contract/src/client.ts

printf '%s\n' '--- chat session hook and construction ---'
rg -n -l --glob '!**/*.test.*' --glob '!**/*.stories.*' \
  'useChatSession|ChatSession|chat-connection' apps/conciv/src packages
rg -n -C 24 --glob '!**/*.test.*' --glob '!**/*.stories.*' \
  'useChatSession|createChat|sendMessage' apps/conciv/src packages/client packages/ui-kit-chat

printf '%s\n' '--- core chat input contract ---'
sed -n '1,220p' packages/core/src/api/rpc/chat.ts
rg -n -C 12 'chat:|send:|ChatContentPartSchema|MultimodalContent|UIMessage' packages/contract/src packages/protocol/src

Repository: conciv-dev/conciv

Length of output: 50373


🏁 Script executed:

set -euo pipefail

cd ../git

printf '%s\n' '--- chat contract and content schema ---'
rg -n -C 24 --glob '!**/*.test.*' \
  'chat\s*:|send\s*:|ChatContentPartSchema|type ChatContent|content:' \
  packages/contract/src packages/protocol/src packages/core/src/api/rpc/chat.ts

printf '%s\n' '--- ORPC websocket serializer source ---'
rg -n -l --glob '*.js' --glob '*.mjs' --glob '*.cjs' \
  'class RPCLink|function RPCLink|websocket.*send|JSON.stringify|input.*json' \
  node_modules/.pnpm/@orpc+client* node_modules/.pnpm/@orpc+server* 2>/dev/null | head -80

printf '%s\n' '--- relevant ORPC implementation excerpts ---'
rg -n -C 20 --glob '*.js' --glob '*.mjs' --glob '*.cjs' \
  'websocket\.send|JSON\.stringify|input.*json|json.*input|message\.' \
  node_modules/.pnpm/@orpc+client* node_modules/.pnpm/@orpc+server* 2>/dev/null | head -500

printf '%s\n' '--- direct send input shape ---'
sed -n '1,180p' packages/contract/src/contract.ts
sed -n '1,180p' packages/core/src/api/rpc/chat.ts

Repository: conciv-dev/conciv

Length of output: 16976


🏁 Script executed:

set -euo pipefail

cd ../git

printf '%s\n' '--- installed ORPC package paths ---'
find node_modules/.pnpm -maxdepth 1 -type d -iname '`@orpc`+client*' -o -iname '`@orpc`+server*' | sort

printf '%s\n' '--- ORPC websocket files ---'
find node_modules/.pnpm -path '*/node_modules/@orpc/client/*' -type f \
  \( -name '*.js' -o -name '*.mjs' -o -name '*.ts' \) -print | sort | head -120

printf '%s\n' '--- ORPC websocket implementation matches ---'
rg -n -C 18 --glob '*.{js,mjs,ts}' \
  'websocket\.send|JSON\.stringify|input.*json|json.*input|procedure|path' \
  node_modules/.pnpm -g '*`@orpc`*' 2>/dev/null | head -600

Repository: conciv-dev/conciv

Length of output: 2599


🏁 Script executed:

set -euo pipefail

cd ../git

ORPC=node_modules/.pnpm/@orpc+client@1.14.7_@opentelemetry+api@1.9.1/node_modules/@orpc/client/dist

printf '%s\n' '--- websocket adapter ---'
cat "$ORPC/adapters/websocket/index.mjs"

printf '%s\n' '--- shared client modules ---'
for file in "$ORPC"/shared/*.mjs; do
  printf '\n--- %s ---\n' "$file"
  rg -n -C 12 'JSON\.stringify|send\(|input|message|path|t:' "$file" | head -240
done

printf '%s\n' '--- server websocket adapter ---'
find node_modules/.pnpm/@orpc+server@1.14.7* -path '*/node_modules/@orpc/server/dist/adapters/websocket/*' \
  -type f -name '*.mjs' -print -exec sed -n '1,260p' {} \;

Repository: conciv-dev/conciv

Length of output: 2241


🏁 Script executed:

set -euo pipefail

cd ../git

printf '%s\n' '--- ORPC peer and standard-server files ---'
find node_modules/.pnpm -path '*/node_modules/@orpc/standard-server-peer/dist/*' -o \
  -path '*/node_modules/@orpc/standard-server/dist/*' -o \
  -path '*/node_modules/@orpc/standard-server-fetch/dist/*' \
  -type f -name '*.mjs' -print | sort | head -120

printf '%s\n' '--- request serialization and peer send logic ---'
rg -n -C 24 --glob '*.mjs' \
  'class ClientPeer|request\(|JSON\.stringify|serialize|message|input|url|path' \
  node_modules/.pnpm/*/node_modules/@orpc/standard-server-peer/dist \
  node_modules/.pnpm/*/node_modules/@orpc/standard-server/dist \
  node_modules/.pnpm/*/node_modules/@orpc/standard-server-fetch/dist \
  node_modules/.pnpm/*/node_modules/@orpc/client/dist/shared 2>/dev/null | head -1000

Repository: conciv-dev/conciv

Length of output: 50374


🏁 Script executed:

set -euo pipefail

cd ../git

printf '%s\n' '--- standard-server-peer package ---'
find node_modules/.pnpm -maxdepth 1 -type d -iname '*standard-server-peer*' -print
find node_modules/.pnpm -path '*/node_modules/@orpc/standard-server-peer/*' \
  -type f \( -name '*.mjs' -o -name '*.js' \) -print | sort

printf '%s\n' '--- peer request frame implementation ---'
rg -n -C 30 --glob '*.{mjs,js}' \
  'class ClientPeer|request\(|JSON\.stringify|message|id|method|path|input|body' \
  node_modules/.pnpm/*standard-server-peer*/node_modules/@orpc/standard-server-peer 2>/dev/null | head -1000

printf '%s\n' '--- package dependency links ---'
readlink -f node_modules/.pnpm/@orpc+client@1.14.7_@opentelemetry+api@1.9.1/node_modules/@orpc/standard-server-peer || true
readlink -f node_modules/.pnpm/@orpc+client@1.14.7_@opentelemetry+api@1.9.1/node_modules/@orpc/standard-server || true

Repository: conciv-dev/conciv

Length of output: 445


🏁 Script executed:

set -euo pipefail

cd ../git

PEER=node_modules/.pnpm/@orpc+standard-server-peer@1.14.7_@opentelemetry+api@1.9.1/node_modules/@orpc/standard-server-peer/dist/index.mjs

printf '%s\n' '--- peer implementation ---'
cat "$PEER"

printf '%s\n' '--- peer exports and package metadata ---'
cat node_modules/.pnpm/@orpc+standard-server-peer@1.14.7_@opentelemetry+api@1.9.1/node_modules/@orpc/standard-server-peer/package.json

Repository: conciv-dev/conciv

Length of output: 23260


🏁 Script executed:

set -euo pipefail

cd ../git

CLIENT=node_modules/.pnpm/@orpc+client@1.14.7_@opentelemetry+api@1.9.1/node_modules/@orpc/client/dist/shared/client.WEeVKeV_.mjs

printf '%s\n' '--- websocket link codec and standard link ---'
sed -n '245,430p' "$CLIENT"

printf '%s\n' '--- stringifyJSON implementation ---'
rg -n -l --glob '*.mjs' 'function stringifyJSON|const stringifyJSON|export.*stringifyJSON' \
  node_modules/.pnpm/*/node_modules/@orpc/shared/dist 2>/dev/null | head -20
rg -n -C 16 --glob '*.mjs' 'function stringifyJSON|const stringifyJSON' \
  node_modules/.pnpm/*/node_modules/@orpc/shared/dist 2>/dev/null | head -120

Repository: conciv-dev/conciv

Length of output: 25648


🏁 Script executed:

set -euo pipefail

cd ../git

node - <<'JS'
const budget = 16 * 1024 * 1024 - 512 * 1024
const sessionId = 'conciv_session'
const runId = 'run-123'
const id = 1

function bytes(value) {
  return new TextEncoder().encode(JSON.stringify(value)).length
}

const content = 'x'.repeat(budget - 3)
const input = {sessionId, runId, content}
const frame = {
  i: id,
  p: {
    u: '/chat/send',
    b: {json: input},
  },
}

const guardedBytes = bytes(content)
const wireBytes = bytes(frame)

console.log(JSON.stringify({
  budget,
  guardedBytes,
  wireBytes,
  omittedBytes: wireBytes - guardedBytes,
  guardPasses: guardedBytes <= budget,
  wireFits: wireBytes <= budget,
  wireFrame: JSON.stringify(frame).slice(0, 180),
}, null, 2))

if (!(guardedBytes <= budget && wireBytes > budget)) {
  throw new Error('The reproduced ORPC frame did not demonstrate the boundary mismatch')
}
JS

Repository: conciv-dev/conciv

Length of output: 522


Measure the complete ORPC WebSocket frame.

chatConnection sends {sessionId, runId, content}, but checkSend measures only JSON.stringify(content). A message can pass while the ORPC envelope exceeds WS_RPC_PAYLOAD_BUDGET_BYTES. Reuse the outbound serializer or include the envelope in the size check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/conciv/src/pane/send-checks.ts` around lines 22 - 27, The checkSend
payload-size validation currently measures only content; update it to serialize
and measure the complete outbound ORPC frame containing sessionId, runId, and
content, reusing the serializer used by chatConnection where available. Keep the
existing rejection response and WS_RPC_PAYLOAD_BUDGET_BYTES threshold unchanged.

omridevk and others added 2 commits August 10, 2026 02:37
Any >16MB frame 1009-closes the shared rpc socket. The image adapter
capped at 20MB raw, which base64-inflates past the limit, so an
11.6-20MB image poisoned the whole tab. Wire limit now lives in
@conciv/protocol/rpc-types as the single source; serve, the adapter
cap, the server-side base64 schema cap, and a new aggregate send-time
guard all derive from it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…churn (#347)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@omridevk
omridevk force-pushed the fix/347-ws-payload-cap branch from 6881562 to 197419f Compare August 9, 2026 23:38
@omridevk
omridevk merged commit 99bbfa8 into main Aug 10, 2026
25 checks passed
@omridevk
omridevk deleted the fix/347-ws-payload-cap branch August 10, 2026 00:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Any >16MB ws rpc input 1009-poisons the shared socket; composer attachments are uncapped

2 participants