Skip to content

feat(peek): share a session to a Slack channel — consent-gated share_session#156

Merged
harry-harish merged 9 commits into
mainfrom
feat/connector-share-session
Jul 9, 2026
Merged

feat(peek): share a session to a Slack channel — consent-gated share_session#156
harry-harish merged 9 commits into
mainfrom
feat/connector-share-session

Conversation

@harry-harish

@harry-harish harry-harish commented Jul 9, 2026

Copy link
Copy Markdown
Member

What

Completes the shared-debugging loop inside Slack: @peek share this session produces the session's portable .peekbundle and uploads it into the thread — only after an explicit egress consent. A teammate downloads it and peek sessions imports it on their own machine (no terminal export step, no cloud sync).

  • share_session MCP tool (peek-mcp) — consent-gated via the same elicitation transport as execute_action, but independent of the Level-3 act gate (it shares recorded data, doesn't act on the live browser). Deny / no-capability / malformed → fail closed, no file. Approve → .peekbundle to a temp file; returns the path (never bytes inline). Audited (share_session, session, approver — no bundle bytes).
  • Bundle lib moved peek-cli → peek-mcp (@peekdev/mcp/session-bundle) so the MCP layer can build bundles; CLI sessions export/import behavior-preserving.
  • SurfaceAdapter.postFile + runtime interceptionshare_session is read-classified, so the brain calls it inline; the connector-core runtime intercepts the result at the callTool boundary (interceptCallTool#postProcessShareSession, wired at the composition root), uploads via the adapter, and deletes the temp bundle in a finally (success or failure).
  • connector-slack postFilefiles.uploadV2 to the thread (needs the files:write scope). The egress consent card renders through the existing consent path.
  • THREATMODEL — new "Session egress" surface: the one path where recorded (masked-at-capture) session data leaves the local-first store for Slack's cloud; consent-gated, temp deleted, honest residual limits.

Notes

  • Import stays CLI (a teammate's machine needs a local peek store anyway) — out of scope here.
  • Slack app config (maintainer): add the files:write scope + reinstall.
  • @peekdev/mcp minor changeset included; connector-core/connector-slack are private:true (no changeset).
  • Egress is never silent — verified fail-closed across approve/deny/no-capability/malformed. Whole-branch review: all invariants pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new session-sharing workflow that can export a portable bundle after explicit approval.
    • Session bundles can now be uploaded directly to supported destinations, with temporary files cleaned up afterward.
    • Audit records now include approved sharing events for traceability.
  • Bug Fixes

    • Improved behavior when sharing is denied or upload support is unavailable, with safer fallback messaging.
    • Updated tool listings so the new sharing option appears consistently across interfaces.

harry-harish and others added 8 commits July 9, 2026 21:06
…layer can build .peekbundle

Move packages/peek-cli/src/lib/session-bundle.ts verbatim to
packages/peek-mcp/src/session-bundle.ts (updating the sha256Hex import
to the local native-host/audit-chain.js copy). Add a ./session-bundle
subpath export to @peekdev/mcp and add tar as a runtime dependency.

Repoint all peek-cli consumers to @peekdev/mcp/session-bundle:
- src/commands/sessions.ts
- src/lib/import-session.ts
- src/commands/sessions.bundle.test.ts
- src/lib/session-bundle.test.ts

Delete the old peek-cli/src/lib/session-bundle.ts (no thin re-export;
zero duplicate logic). All existing bundle/import tests pass unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Adds the share_session MCP tool: given a sessionId (and optional surface
name, default 'Slack'), elicits an explicit egress consent card via MCP
elicitInput before producing a .peekbundle temp file. The design:

• Egress is never silent — if the client lacks elicitation capability the
  tool denies unconditionally; deny / cancel also denies. No bundle is
  written without an explicit approve.
• NOT tied to the Level-3 act gate — sharing already-recorded data is
  distinct from acting on the live browser; the consent card IS the
  authorization, level-independent.
• On approve: loads session events + console/network rows from the local DB
  (same read paths as generate_playwright_repro + get_session_*), calls
  packBundle (from the Task-1 session-bundle lib) to a temp file in
  os.tmpdir() named <sanitized_sessionId>.peekbundle, returns
  { ok, bundlePath, filename, sizeBytes, caveat: FULLSNAPSHOT_CAVEAT }.
  Bundle bytes are never returned inline.
• On deny: returns { ok: false, result: 'denied' }, no file written.
• Audit: every call (approve or deny) writes a share_session audit entry
  (tool + sessionId + surface + approver). Bundle bytes/content never reach
  the audit log.

Additions:
- elicitation.ts: buildEgressConsentMessage() — egress-specific consent copy
  (distinct from the Action-verb buildElicitMessage used by execute_action).
- audit.ts: AuditTool union widened to include 'share_session'.
- server.ts: registerTools() adds tool #19 share_session; dispatchShareSession()
  implements the consent-check + db-load + packBundle + audit path.
- PEEK_MCP_TOOLS constant updated (20 tools).
- test/share-session.test.ts: 9 tests covering (a) deny→no file, (b) approve→
  valid verifyBundle-passing bundle, (c) approved call writes share_session
  audit entry (no bytes), plus no-capability→deny, unknown-session error,
  and tool-list registration.
- test/server.test.ts: updated tool count 19→20.
- .changeset/peek-share-session-tool.md: minor changeset.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
… tools)

Bring stdio-smoke.test.ts and server.test.ts in sync with the new tool
count after adding share_session in the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
…Sync guard, injection sanitize, audit approver, test gaps)

Fix 1: bundle filename now includes a Date.now() nonce (${safeId}-${nonce}.peekbundle)
so two concurrent approved exports of the same session produce distinct temp files.
Fix 2: statSync moved inside the packBundle try/catch so a post-approve FS error
returns {ok:false} instead of propagating as an unhandled MCP throw.
Fix 3: sessionId sanitized (strip non-printable / control chars) before interpolation
into the egress consent card text — prevents newline injection into the card.
Fix 4: no-elicitation-capability deny now records approver:'connector-elicit' instead
of 'user' (user was never asked); actual user decline/cancel keeps approver:'user'.
Tests: cancel test gains no-file assertion; new unknown-action test locks fail-closed
behavior; decline file check updated to pattern scan (robust after nonce addition).
Gate condition (!elicitOutcome.elicited || elicitOutcome.verdict === 'deny') unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
…terception

Add optional SurfaceAdapter.postFile? hook (conversationId, filePath,
filename, comment?) and intercept approved share_session tool results in
ConnectorRuntime.handleConsentResponse: upload via adapter.postFile, then
delete the temp bundle (try/finally — file is always removed, even on upload
failure). Denied results and adapters without postFile degrade gracefully.

Four new tests: approved share_session → postFile called + file deleted;
upload failure → file still deleted (try/finally); {ok:false} → no postFile,
no throw; no postFile on adapter → text note, no crash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
…ool boundary

share_session is classified 'read' by classify(), so SdkBrain.runTurn runs it
inline via its injected callTool and feeds the {ok,bundlePath} result straight
back into the tool-use loop — it never emits a {kind:'consent'} outcome. The
09219fc interception lived in handleConsentResponse (consent-only path), so the
bundle upload + temp-file cleanup never fired in production; the old tests
passed only because a synthetic brain forced the suspend path.

Fix (wiring option a): the runtime now exposes interceptCallTool(name, input,
inner). The connector-slack composition root hands the brain
runtime.interceptCallTool(...) instead of the bare mcp.callTool, so an approved
share_session result triggers adapter.postFile + temp-file cleanup on the REAL
inline path regardless of classification. Chosen over a PeekMcp post-tool hook
because the post-processing needs the surface adapter + active conversationId,
which the runtime holds and PeekMcp deliberately does not — keeping PeekMcp a
pure transport and the brain seeing a plain (name,input)=>Promise<string>.

- Remove the share_session block from handleConsentResponse (wrong path).
- Add interceptCallTool() + private #postProcessShareSession(): postFile in try,
  temp delete in finally; {ok:false}/unparseable → unchanged; no postFile →
  filename-only degrade note (no raw temp path leak); upload error → surfaced to
  the brain so the turn continues.
- Replace the four synthetic-consent-brain tests with four that drive the REAL
  SdkBrain (read/inline routing). The MANDATORY test asserts no consent card is
  posted, postFile fires with the active conversationId, and the temp file is
  deleted — it fails against the 09219fc wrong-path code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Implements SurfaceAdapter.postFile on SlackAdapter using files.uploadV2.
Reads the temp bundle with readFileSync; branches on threadTs presence to
satisfy the FileThreadDestinationArgument vs FileChannelDestinationArgument
discriminated union under exactOptionalPropertyTypes. Temp-file deletion
stays with the runtime (Task 3). Three new tests cover the threaded,
slash-command (no thread_ts), and upload-error paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Add §"Session egress — share_session (SP5 / connector upload)" documenting
the one path where a recorded session bundle leaves peek's local-first store
and is uploaded to a third-party cloud (Slack via files.uploadV2). Covers:
what the bundle contains (masked-at-capture DOM + console/network), the
explicit egress consent card gate, the files:write scope requirement, temp-file
lifecycle (deleted after upload, success or failure), audit log entry format,
and honest residual limits (Slack retention + capture-time masking caveat).
Also adds attack surface #9 to the enumeration at the top of the file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@harry-harish, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a03aadb8-6aec-47f9-b6e7-4b76206c6b04

📥 Commits

Reviewing files that changed from the base of the PR and between d6f7ada and d86af0d.

📒 Files selected for processing (6)
  • packages/connector-core/src/runtime.ts
  • packages/connector-slack/src/slack-adapter.test.ts
  • packages/connector-slack/src/slack-adapter.ts
  • packages/peek-mcp/src/mcp/elicitation.ts
  • packages/peek-mcp/src/mcp/server.ts
  • packages/peek-mcp/test/share-session.test.ts
📝 Walkthrough

Walkthrough

Adds a new consent-gated share_session MCP tool that exports masked session data as a .peekbundle after explicit egress approval, with audit logging. Wires ConnectorRuntime.interceptCallTool and SlackAdapter.postFile for upload/cleanup, exposes @peekdev/mcp/session-bundle as a shared package, and migrates peek-cli to use it.

Changes

share_session feature

Layer / File(s) Summary
share_session MCP tool
packages/peek-mcp/src/mcp/server.ts, packages/peek-mcp/src/mcp/elicitation.ts, packages/peek-mcp/src/native-host/audit.ts
Adds buildEgressConsentMessage, dispatchShareSession (consent probe, deny/approve paths, bundle packing, audit logging), tool registration, and extends AuditTool union.
Session-bundle shared package
packages/peek-mcp/package.json, packages/peek-mcp/src/session-bundle.ts
Exposes ./session-bundle export subpath, adds tar dependency, fixes sha256Hex import path.
share_session tests
packages/peek-mcp/test/share-session.test.ts, packages/peek-mcp/test/server.test.ts, packages/peek-mcp/test/stdio-smoke.test.ts
Adds deny/approve/error/registration tests and updates expected tool counts to 20.
Runtime upload interception
packages/connector-core/src/runtime.ts, packages/connector-core/src/surface.ts, packages/connector-core/src/runtime.test.ts
Adds interceptCallTool and optional postFile on SurfaceAdapter; handles upload, fallback notes, and temp-file cleanup; adds tests.
Slack adapter upload + wiring
packages/connector-slack/src/slack-adapter.ts, packages/connector-slack/src/slack-adapter.test.ts, packages/connector-slack/src/index.ts
Implements SlackAdapter.postFile via files.uploadV2, routes SdkBrain.callTool through runtimeRef.interceptCallTool.
peek-cli migration
packages/peek-cli/src/commands/sessions.ts, packages/peek-cli/src/commands/sessions.bundle.test.ts, packages/peek-cli/src/lib/import-session.ts, packages/peek-cli/src/lib/session-bundle.test.ts
Switches imports of bundle codec functions from local module to @peekdev/mcp/session-bundle.
Threat model docs
docs/peek/THREATMODEL.md
Documents egress attack surface, consent gate, Slack upload path, temp-file lifecycle, audit logging, and residual limits for share_session.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server as peek-mcp server
  participant Store as SQLite store
  participant FS as Temp file system

  Client->>Server: call share_session(sessionId, surface)
  Server->>Client: elicitConsent(buildEgressConsentMessage)
  alt denied
    Client-->>Server: decline/cancel
    Server->>Server: append audit entry (result: denied)
    Server-->>Client: ok: false, result: denied
  else approved
    Client-->>Server: accept
    Server->>Store: load session summary, events, console/network rows
    Server->>FS: packBundle -> write .peekbundle
    Server->>Server: append audit entry (result: ok)
    Server-->>Client: ok: true, bundlePath, filename, sizeBytes, caveat
  end
Loading
sequenceDiagram
  participant Brain as SdkBrain
  participant Runtime as ConnectorRuntime
  participant MCP as MCP server
  participant Adapter as SlackAdapter

  Brain->>Runtime: interceptCallTool(share_session, input, inner)
  Runtime->>MCP: inner(share_session, input)
  MCP-->>Runtime: ok, bundlePath, filename
  alt postFile available and ok
    Runtime->>Adapter: postFile(conversationId, bundlePath, filename)
    Adapter->>Adapter: files.uploadV2 via Slack API
    Adapter-->>Runtime: success
  else postFile unavailable or ok:false
    Runtime->>Runtime: build fallback note
  end
  Runtime->>Runtime: rm(bundlePath, force)
  Runtime-->>Brain: result text
Loading

Possibly related PRs

  • Cubenest/rrweb-stack#119: Implements the local .peekbundle codec and CLI export/import flows that this PR replaces with the shared @peekdev/mcp/session-bundle package.
  • Cubenest/rrweb-stack#140: Introduced the connector-core/connector-slack runtime and adapter structure that this PR extends with interceptCallTool and postFile for share_session egress.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main change: consent-gated session sharing via the new share_session tool, including Slack delivery.
Docstring Coverage ✅ Passed Docstring coverage is 84.62% which is sufficient. The required threshold is 80.00%.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/connector-share-session

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 3

🧹 Nitpick comments (5)
packages/connector-slack/src/slack-adapter.ts (1)

1-1: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider an async read instead of readFileSync to avoid blocking the event loop.

readFileSync blocks the single Node.js thread for the duration of the read, which delays every other Slack event/turn this process is handling while a (potentially large) .peekbundle is loaded into memory. Streams are not a safe substitute here — files.uploadV2 has had recurring stream-compatibility bugs (e.g. failing to recognize non-fs.ReadStream readables), so keeping a Buffer is the right call. Swapping to the async Buffer-returning readFile avoids the block without touching the upload-argument shape.

♻️ Proposed fix
-import { readFileSync } from 'node:fs';
+import { readFile } from 'node:fs/promises';
   async postFile(
     conversationId: string,
     filePath: string,
     filename: string,
     comment?: string,
   ): Promise<void> {
     const r = this.route(conversationId);
-    const file = readFileSync(filePath);
+    const file = await readFile(filePath);

Also applies to: 156-185

🤖 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/connector-slack/src/slack-adapter.ts` at line 1, The Slack adapter
currently uses readFileSync, which blocks the event loop while loading a
.peekbundle and can delay other Slack turns; update the file-loading path in
slack-adapter.ts to use the async Buffer-returning readFile instead. Keep the
upload payload as a Buffer for files.uploadV2 compatibility, and adjust the
surrounding async flow where the bundle is read and passed into the upload logic
so the existing upload argument shape stays unchanged.
packages/peek-mcp/src/mcp/server.ts (3)

1161-1169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bound surface the same way other free-text tool args are bounded.

Every other short free-text field in this file (text in set_intent, prompt in request_user_input, label in suggest_element) has a .max() cap. surface has none, so an arbitrarily long string ends up in the consent card and the audit log args.

🤖 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/peek-mcp/src/mcp/server.ts` around lines 1161 - 1169, The `surface`
field in the tool input schema is an unbounded free-text argument, unlike the
similar capped fields in `set_intent`, `request_user_input`, and
`suggest_element`. Add a reasonable `.max()` limit to `surface` in `server.ts`’s
tool schema, keeping the existing default and description intact, so excessively
long values cannot flow into the consent card or audit-log args.

1448-1492: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Raw SQL bypasses the existing queries.ts module boundary.

Every other tool in this file goes through queries.ts helpers (getSessionSummaryRow, getConsoleErrors, getNetworkErrors, etc.). Here, dispatchShareSession hand-rolls two handle.prepare(...).all(...) queries directly against console_events/network_events (needed because it wants all rows, not just errors). Extracting these into named helpers in queries.ts (e.g. getAllConsoleEvents, getAllNetworkEvents) would keep DB access centralized and unit-testable independent of the MCP transport, consistent with the rest of the file.

🤖 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/peek-mcp/src/mcp/server.ts` around lines 1448 - 1492,
dispatchShareSession is bypassing the queries.ts boundary by calling
handle.prepare(...).all(...) directly for console_events and network_events.
Move these raw SQL reads into new named helpers in queries.ts, such as
getAllConsoleEvents and getAllNetworkEvents, and have dispatchShareSession call
those helpers instead. Keep the existing mapping of ts/level/message/stack and
ts/method/url/status/statusText/resourceType/durationMs/errorText in the helper
return shape so the bundle payload stays unchanged.

1143-1180: 🔒 Security & Privacy | 🔵 Trivial

Orphaned .peekbundle temp files for non-connector clients.

The comment correctly notes "connector-core deletes it after upload," but that only holds for the Slack path. Any other MCP client (Claude Desktop, MCP Inspector, a future connector without postFile) that calls share_session and gets ok:true leaves a masked-but-real session export sitting in the OS temp dir indefinitely, since nothing else in this tool cleans it up. Given peek's local-first/no-persistence posture, is there (or should there be) a fallback TTL sweep for orphaned .peekbundle files, independent of the connector upload path?

🤖 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/peek-mcp/src/mcp/server.ts` around lines 1143 - 1180, The
share_session tool can leave approved .peekbundle exports orphaned for
non-connector clients because cleanup currently depends on connector-core
uploading and deleting the temp file. Update dispatchShareSession and the
share_session flow to ensure every approved export gets a fallback cleanup path,
such as a TTL-based sweep or a post-response temp-file cleanup mechanism, while
preserving the current connector upload behavior. Locate the export lifecycle
around share_session, dispatchShareSession, and any temp file creation/return
logic, and make sure orphaned bundle paths are not left behind for Claude
Desktop, MCP Inspector, or future clients.
packages/peek-mcp/test/share-session.test.ts (1)

380-399: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test title promises an audit-log assertion that isn't there.

The test is titled "(c) approve with unknown session → error (no file, no audit ok entry)" but only asserts body.ok/body.result; it never reads auditLogPath to confirm no (or a specific) audit entry was written. Given the audit-gap in dispatchShareSession for this exact branch (no appendAuditEntry call at all on session-not-found), adding the assertion here would have caught it.

✅ Proposed addition
       const body = parseJson(res as never) as Record<string, unknown>;
       // Should return an error (session not found).
       expect(body.ok).toBe(false);
       expect(body.result).toBe('error');
+      // No audit entry should be silently dropped for an approved-but-failed export.
+      const contents = existsSync(auditLogPath) ? readFileSync(auditLogPath, 'utf8') : '';
+      const lines = contents.split('\n').filter((l) => l.length > 0);
+      expect(lines).toHaveLength(1);
🤖 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/peek-mcp/test/share-session.test.ts` around lines 380 - 399, The
test name claims it verifies the audit log, but the body only checks the tool
response and never inspects auditLogPath. Update this share_session test to read
the audit log after calling client.callTool and assert the expected audit
behavior for the unknown-session branch (specifically that no success/ok audit
entry was written), using the existing helpers around stubElicitation,
connectClient, and dispatchShareSession flow to keep the assertion aligned with
the session-not-found case.
🤖 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 `@packages/connector-core/src/runtime.ts`:
- Around line 168-196: The cleanup in `#postProcessShareSession` should not
override the result of a successful upload or an upload error. Keep the
rm(bundlePath, { force: true }) call in the finally block, but catch and swallow
or separately log cleanup failures so they do not replace the return value from
the try/catch paths. Preserve the existing success and upload-failure messages,
and only let unexpected errors from the main upload flow propagate.

In `@packages/peek-mcp/src/mcp/elicitation.ts`:
- Around line 112-120: The consent message builder in buildEgressConsentMessage
sanitizes sessionId but interpolates surface raw into user-facing text. Apply
the same control-character scrubbing to surface before composing the consent
card, and use the sanitized value in the returned string so a caller-supplied
surface cannot inject or spoof the approval prompt.

In `@packages/peek-mcp/src/mcp/server.ts`:
- Around line 1383-1554: `dispatchShareSession` is missing audit writes on
approved paths that return errors, so consented egress attempts can disappear
from the audit log. Add the same `appendAuditEntry` handling used in the deny
and success branches to the no-DB, missing-session, event-load, and bundle-pack
error returns, using a `DraftAuditEntry` with `approver: 'connector-elicit'`,
`result: 'error'`, and the relevant `error` text. Keep each audit write wrapped
in its own try/catch so an audit failure does not mask the original `jsonResult`
error.

---

Nitpick comments:
In `@packages/connector-slack/src/slack-adapter.ts`:
- Line 1: The Slack adapter currently uses readFileSync, which blocks the event
loop while loading a .peekbundle and can delay other Slack turns; update the
file-loading path in slack-adapter.ts to use the async Buffer-returning readFile
instead. Keep the upload payload as a Buffer for files.uploadV2 compatibility,
and adjust the surrounding async flow where the bundle is read and passed into
the upload logic so the existing upload argument shape stays unchanged.

In `@packages/peek-mcp/src/mcp/server.ts`:
- Around line 1161-1169: The `surface` field in the tool input schema is an
unbounded free-text argument, unlike the similar capped fields in `set_intent`,
`request_user_input`, and `suggest_element`. Add a reasonable `.max()` limit to
`surface` in `server.ts`’s tool schema, keeping the existing default and
description intact, so excessively long values cannot flow into the consent card
or audit-log args.
- Around line 1448-1492: dispatchShareSession is bypassing the queries.ts
boundary by calling handle.prepare(...).all(...) directly for console_events and
network_events. Move these raw SQL reads into new named helpers in queries.ts,
such as getAllConsoleEvents and getAllNetworkEvents, and have
dispatchShareSession call those helpers instead. Keep the existing mapping of
ts/level/message/stack and
ts/method/url/status/statusText/resourceType/durationMs/errorText in the helper
return shape so the bundle payload stays unchanged.
- Around line 1143-1180: The share_session tool can leave approved .peekbundle
exports orphaned for non-connector clients because cleanup currently depends on
connector-core uploading and deleting the temp file. Update dispatchShareSession
and the share_session flow to ensure every approved export gets a fallback
cleanup path, such as a TTL-based sweep or a post-response temp-file cleanup
mechanism, while preserving the current connector upload behavior. Locate the
export lifecycle around share_session, dispatchShareSession, and any temp file
creation/return logic, and make sure orphaned bundle paths are not left behind
for Claude Desktop, MCP Inspector, or future clients.

In `@packages/peek-mcp/test/share-session.test.ts`:
- Around line 380-399: The test name claims it verifies the audit log, but the
body only checks the tool response and never inspects auditLogPath. Update this
share_session test to read the audit log after calling client.callTool and
assert the expected audit behavior for the unknown-session branch (specifically
that no success/ok audit entry was written), using the existing helpers around
stubElicitation, connectClient, and dispatchShareSession flow to keep the
assertion aligned with the session-not-found case.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 92e316e7-d049-43f9-8a08-e424d4228590

📥 Commits

Reviewing files that changed from the base of the PR and between 844d7b5 and d6f7ada.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (20)
  • .changeset/peek-share-session-tool.md
  • docs/peek/THREATMODEL.md
  • packages/connector-core/src/runtime.test.ts
  • packages/connector-core/src/runtime.ts
  • packages/connector-core/src/surface.ts
  • packages/connector-slack/src/index.ts
  • packages/connector-slack/src/slack-adapter.test.ts
  • packages/connector-slack/src/slack-adapter.ts
  • packages/peek-cli/src/commands/sessions.bundle.test.ts
  • packages/peek-cli/src/commands/sessions.ts
  • packages/peek-cli/src/lib/import-session.ts
  • packages/peek-cli/src/lib/session-bundle.test.ts
  • packages/peek-mcp/package.json
  • packages/peek-mcp/src/mcp/elicitation.ts
  • packages/peek-mcp/src/mcp/server.ts
  • packages/peek-mcp/src/native-host/audit.ts
  • packages/peek-mcp/src/session-bundle.ts
  • packages/peek-mcp/test/server.test.ts
  • packages/peek-mcp/test/share-session.test.ts
  • packages/peek-mcp/test/stdio-smoke.test.ts

Comment thread packages/connector-core/src/runtime.ts
Comment thread packages/peek-mcp/src/mcp/elicitation.ts
Comment thread packages/peek-mcp/src/mcp/server.ts
… nitpicks)

MAJOR 1 (connector-core/runtime.ts): wrap rm() in finally in its own
try/catch so an EACCES/EBUSY cleanup failure never clobbers the real
upload result returned by the try/catch above.

MAJOR 2 (peek-mcp/elicitation.ts): sanitize surface like sessionId —
add safeSurface = surface.replace(/[^\x20-\x7E]/g,'_').slice(0,60)
before interpolating into the egress consent card to prevent
newline/control-char injection.

MAJOR 3 (peek-mcp/server.ts): add appendAuditEntry (result:'error',
approver:'connector-elicit') to all 4 post-consent error returns
(no DB, session-not-found, event-load failure, pack failure), each
wrapped in its own try/catch. Add test: approved unknown session →
audit entry with result:'error' and approver:'connector-elicit'.

NITPICK A (connector-slack/slack-adapter.ts): switch readFileSync →
async readFile from node:fs/promises; update test mock accordingly.

NITPICK B (server.ts): add .max(60) cap to surface zod schema.

NITPICK C (server.ts): document temp-file ownership in tool description
(connector uploads-and-deletes; direct caller is responsible).

Skipped: audit.ts:380-399 (pre-existing, 0 diff lines in this PR).
Deferred: server.ts 1448-1492 SQL helpers (in diff but non-trivial
refactor, correctness-neutral, follow-up cleanup).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
@harry-harish

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@harry-harish harry-harish merged commit 22743c0 into main Jul 9, 2026
7 checks passed
@harry-harish harry-harish deleted the feat/connector-share-session branch July 9, 2026 17:04
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.

1 participant