AR-267 outbound webhook SDK client#281
Conversation
📝 WalkthroughWalkthroughThe TypeScript SDK gains five outbound webhook management methods ( ChangesSDK Outbound Webhook Feature and contentHash
Trajectory Metadata Updates
Sequence Diagram(s)sequenceDiagram
participant Caller
participant RelayFileClient
participant REST_API
Caller->>RelayFileClient: registerWebhook({workspaceId, url, pathGlobs, secret})
RelayFileClient->>REST_API: POST /v1/workspaces/{id}/webhooks
REST_API-->>RelayFileClient: RegisterWebhookResponse {subscriptionId}
RelayFileClient-->>Caller: RegisterWebhookResponse
Caller->>RelayFileClient: getWebhookDeadLetters(workspaceId, {cursor, limit})
RelayFileClient->>REST_API: GET /v1/workspaces/{id}/webhooks/dead-letters?cursor&limit
REST_API-->>RelayFileClient: WebhookDeliveryDeadLetterFeedResponse
RelayFileClient-->>Caller: WebhookDeliveryDeadLetterFeedResponse
Caller->>RelayFileClient: replayWebhookDeadLetter(workspaceId, deliveryId)
RelayFileClient->>REST_API: POST /v1/workspaces/{id}/webhooks/dead-letters/{deliveryId}/replay
REST_API-->>RelayFileClient: QueuedResponse
RelayFileClient-->>Caller: QueuedResponse
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces webhook and dead-letter queue (DLQ) management capabilities to the TypeScript SDK, including methods to register, list, and delete webhooks, as well as retrieve and replay webhook dead letters. It also adds corresponding TypeScript types and unit tests. The feedback suggests adding defensive checks to ensure required parameters like workspaceId, url, subscriptionId, and deliveryId are provided and non-empty, preventing malformed requests or unintended API calls.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| async registerWebhook(input: RegisterWebhookInput): Promise<RegisterWebhookResponse> { | ||
| return this.request<RegisterWebhookResponse>({ | ||
| method: "POST", | ||
| path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/webhooks`, | ||
| correlationId: input.correlationId, | ||
| body: { | ||
| url: input.url, | ||
| pathGlobs: input.pathGlobs, | ||
| secret: input.secret | ||
| }, | ||
| signal: input.signal | ||
| }); | ||
| } |
There was a problem hiding this comment.
Add defensive checks to ensure workspaceId and url are provided and non-empty. This prevents making malformed requests or hitting incorrect endpoints at runtime.
async registerWebhook(input: RegisterWebhookInput): Promise<RegisterWebhookResponse> {
if (!input.workspaceId) {
throw new Error("workspaceId is required");
}
if (!input.url) {
throw new Error("url is required");
}
return this.request<RegisterWebhookResponse>({
method: "POST",
path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/webhooks`,
correlationId: input.correlationId,
body: {
url: input.url,
pathGlobs: input.pathGlobs,
secret: input.secret
},
signal: input.signal
});
}| async deleteWebhook( | ||
| workspaceId: string, | ||
| subscriptionId: string, | ||
| options: DeleteWebhookOptions = {} | ||
| ): Promise<void> { | ||
| await this.performRequest({ | ||
| method: "DELETE", | ||
| path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/${encodeURIComponent(subscriptionId)}`, | ||
| correlationId: options.correlationId, | ||
| signal: options.signal | ||
| }); | ||
| } |
There was a problem hiding this comment.
Add defensive checks to ensure workspaceId and subscriptionId are provided and non-empty. If subscriptionId is empty, the resulting URL path resolves to the collection endpoint, which could lead to accidental bulk deletion or calling the wrong endpoint.
async deleteWebhook(
workspaceId: string,
subscriptionId: string,
options: DeleteWebhookOptions = {}
): Promise<void> {
if (!workspaceId) {
throw new Error("workspaceId is required");
}
if (!subscriptionId) {
throw new Error("subscriptionId is required");
}
await this.performRequest({
method: "DELETE",
path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/${encodeURIComponent(subscriptionId)}`,
correlationId: options.correlationId,
signal: options.signal
});
}| async getWebhookDeadLetters( | ||
| workspaceId: string, | ||
| options: GetWebhookDeadLettersOptions = {} | ||
| ): Promise<WebhookDeliveryDeadLetterFeedResponse> { | ||
| const query = buildQuery({ | ||
| cursor: options.cursor, | ||
| limit: options.limit | ||
| }); | ||
| return this.request<WebhookDeliveryDeadLetterFeedResponse>({ | ||
| method: "GET", | ||
| path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/dlq${query}`, | ||
| correlationId: options.correlationId, | ||
| signal: options.signal | ||
| }); | ||
| } |
There was a problem hiding this comment.
Add a defensive check to ensure workspaceId is provided and non-empty to prevent malformed request paths.
async getWebhookDeadLetters(
workspaceId: string,
options: GetWebhookDeadLettersOptions = {}
): Promise<WebhookDeliveryDeadLetterFeedResponse> {
if (!workspaceId) {
throw new Error("workspaceId is required");
}
const query = buildQuery({
cursor: options.cursor,
limit: options.limit
});
return this.request<WebhookDeliveryDeadLetterFeedResponse>({
method: "GET",
path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/dlq${query}`,
correlationId: options.correlationId,
signal: options.signal
});
}| async replayWebhookDeadLetter( | ||
| workspaceId: string, | ||
| deliveryId: string, | ||
| correlationId?: string, | ||
| signal?: AbortSignal | ||
| ): Promise<QueuedResponse> { | ||
| return this.request<QueuedResponse>({ | ||
| method: "POST", | ||
| path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/dlq/${encodeURIComponent(deliveryId)}/replay`, | ||
| correlationId, | ||
| signal | ||
| }); | ||
| } |
There was a problem hiding this comment.
Add defensive checks to ensure workspaceId and deliveryId are provided and non-empty to prevent malformed request paths.
async replayWebhookDeadLetter(
workspaceId: string,
deliveryId: string,
correlationId?: string,
signal?: AbortSignal
): Promise<QueuedResponse> {
if (!workspaceId) {
throw new Error("workspaceId is required");
}
if (!deliveryId) {
throw new Error("deliveryId is required");
}
return this.request<QueuedResponse>({
method: "POST",
path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/dlq/${encodeURIComponent(deliveryId)}/replay`,
correlationId,
signal
});
}There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 48936510d7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| async registerWebhook(input: RegisterWebhookInput): Promise<RegisterWebhookResponse> { | ||
| return this.request<RegisterWebhookResponse>({ | ||
| method: "POST", | ||
| path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/webhooks`, |
There was a problem hiding this comment.
Don’t publish SDK calls for unhandled webhook routes
These new methods target /v1/workspaces/{workspaceId}/webhooks, but I checked the HTTP router and OpenAPI contract: internal/httpapi/server.go:228-230 only matches /webhooks/ingest, and the spec only documents that ingest path. As a result, registerWebhook, listWebhooks, deleteWebhook, and the /webhooks/dlq... calls all fall through to 404 against the current service, making the outbound webhook SDK feature unusable unless the server/spec routes are added or the SDK paths are changed.
Useful? React with 👍 / 👎.
| type: FilesystemEventType; | ||
| path: string; | ||
| revision: string; | ||
| contentHash?: string; |
There was a problem hiding this comment.
Preserve contentHash through RelayFileSync events
When events are consumed through RelayFileSync, this newly advertised field is still stripped: sync.ts reconstructs events in normalizeFilesystemEvent from a fixed set of fields and RelayFileSyncWireEvent has no contentHash, so server WebSocket payloads that include the hash arrive at onEvent with contentHash undefined. Consumers relying on the new typed field for skip-read/dedup logic only get it from raw REST/raw WebSocket paths, not from the higher-level sync API.
Useful? React with 👍 / 👎.
| async registerWebhook(input: RegisterWebhookInput): Promise<RegisterWebhookResponse> { | ||
| return this.request<RegisterWebhookResponse>({ | ||
| method: "POST", | ||
| path: `/v1/workspaces/${encodeURIComponent(input.workspaceId)}/webhooks`, | ||
| correlationId: input.correlationId, | ||
| body: { | ||
| url: input.url, | ||
| pathGlobs: input.pathGlobs, | ||
| secret: input.secret | ||
| }, | ||
| signal: input.signal | ||
| }); | ||
| } | ||
|
|
||
| async listWebhooks( | ||
| workspaceId: string, | ||
| options: ListWebhooksOptions = {} | ||
| ): Promise<WebhookSubscription[]> { | ||
| return this.request<WebhookSubscription[]>({ | ||
| method: "GET", | ||
| path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks`, | ||
| correlationId: options.correlationId, | ||
| signal: options.signal | ||
| }); | ||
| } | ||
|
|
||
| async deleteWebhook( | ||
| workspaceId: string, | ||
| subscriptionId: string, | ||
| options: DeleteWebhookOptions = {} | ||
| ): Promise<void> { | ||
| await this.performRequest({ | ||
| method: "DELETE", | ||
| path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/${encodeURIComponent(subscriptionId)}`, | ||
| correlationId: options.correlationId, | ||
| signal: options.signal | ||
| }); | ||
| } | ||
|
|
||
| async getWebhookDeadLetters( | ||
| workspaceId: string, | ||
| options: GetWebhookDeadLettersOptions = {} | ||
| ): Promise<WebhookDeliveryDeadLetterFeedResponse> { | ||
| const query = buildQuery({ | ||
| cursor: options.cursor, | ||
| limit: options.limit | ||
| }); | ||
| return this.request<WebhookDeliveryDeadLetterFeedResponse>({ | ||
| method: "GET", | ||
| path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/dlq${query}`, | ||
| correlationId: options.correlationId, | ||
| signal: options.signal | ||
| }); | ||
| } | ||
|
|
||
| async replayWebhookDeadLetter( | ||
| workspaceId: string, | ||
| deliveryId: string, | ||
| correlationId?: string, | ||
| signal?: AbortSignal | ||
| ): Promise<QueuedResponse> { | ||
| return this.request<QueuedResponse>({ | ||
| method: "POST", | ||
| path: `/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/dlq/${encodeURIComponent(deliveryId)}/replay`, | ||
| correlationId, | ||
| signal | ||
| }); | ||
| } |
There was a problem hiding this comment.
🚩 SDK methods reference server endpoints that don't exist yet
The new methods (registerWebhook, listWebhooks, deleteWebhook, getWebhookDeadLetters, replayWebhookDeadLetter) target API paths like POST /v1/workspaces/{id}/webhooks, GET /v1/workspaces/{id}/webhooks, DELETE /v1/workspaces/{id}/webhooks/{subId}, GET /v1/workspaces/{id}/webhooks/dlq, and POST /v1/workspaces/{id}/webhooks/dlq/{deliveryId}/replay — none of which exist in internal/httpapi/server.go (only webhooks/ingest is implemented). The OpenAPI spec also only defines the /webhooks/ingest path. This means calling these SDK methods against the current server will result in 404 errors. This is likely intentional SDK-first development, but there's no corresponding server PR linked. The AGENTS.md rule about OpenAPI sync applies when adding handlers (not SDK methods), so this isn't a rule violation, but it's worth confirming the server-side implementation is planned.
Was this helpful? React with 👍 or 👎 to provide feedback.
Findings
Addressed Comments
Advisory Notes
Verification
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 @.trajectories/completed/2026-06/traj_4pvrlmqfnzng/trajectory.json:
- Around line 39-48: The decision event's content field contains a redundant
phrase repeated twice separated by a colon: "Leave outbound webhook SDK/server
contract mismatch as review finding: Leave outbound webhook SDK/server contract
mismatch as review finding". Remove the duplication by keeping only a single
instance of the phrase in the content field, aligning it with the chosen value
in the raw object so both contain the same non-duplicated text.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: bef4f194-4bb0-430c-9a32-c2f8420e0b00
📒 Files selected for processing (9)
.agentworkforce/trajectories/active/traj_pmzcmccpttxb/trajectory.json.trajectories/active/traj_4pvrlmqfnzng/trajectory.json.trajectories/completed/2026-06/traj_4pvrlmqfnzng/summary.md.trajectories/completed/2026-06/traj_4pvrlmqfnzng/trajectory.json.trajectories/index.jsonpackages/sdk/typescript/src/client.test.tspackages/sdk/typescript/src/client.tspackages/sdk/typescript/src/index.tspackages/sdk/typescript/src/types.ts
💤 Files with no reviewable changes (2)
- .trajectories/active/traj_4pvrlmqfnzng/trajectory.json
- .trajectories/index.json
|
Findings
Addressed Comments
Advisory Notes
Verification
Not ready: the PR still has a blocking SDK/server/OpenAPI contract mismatch and the full CI-equivalent path could not be completed. |
- Regenerate root package-lock.json to 0.8.28 mount packages, fixing the stale-lock `npm ci` failures across Contract, E2E, SDK Typecheck, and Provider-backed evals (lock still pinned mount-* @ 0.8.27 after the v0.8.28 release bump). - Add defensive required-arg guards to registerWebhook/listWebhooks/ deleteWebhook/getWebhookDeadLetters/replayWebhookDeadLetter so empty workspaceId/url/subscriptionId/deliveryId throw instead of issuing a malformed request (addresses gemini-code-assist findings). - Carry contentHash through RelayFileSyncWireEvent and normalizeFilesystemEvent so the new typed field survives the higher-level sync API, not just raw REST/WS (addresses codex P2 finding). - Add tests for the validation guards and contentHash passthrough. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pushed CI (all four failures) — root cause was a stale root gemini-code-assist (defensive checks) — added required-arg guards to codex P2 (contentHash stripped through sync) — added codex P1 / Devin (server routes don't exist) — false positive. These reviewers checked the Go reference server ( |
Relayfile Eval ReviewRun: Passed: 4 | Needs human: 0 | Reviewable: 0 | Missing output: 0 | Failed: 0 | Skipped: 0 Human Review CasesNo reviewable human-review cases captured Relayfile output. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/sdk/typescript/src/sync.ts`:
- Line 142: The contentHash field declared with type annotation at line 142 is
being forwarded from an untrusted WebSocket payload at line 258 without runtime
type validation. This can cause type violations if the payload contains a
non-string value (such as an object or number). Add a runtime type guard before
the normalization event is emitted at line 258 to validate that contentHash is
either a string or undefined, ensuring it conforms to the declared type contract
and prevents downstream consumers from receiving invalid types. Only proceed
with emitting the FilesystemEvent if the contentHash passes validation.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0a8d8682-dd37-4229-8fea-b05c38516a97
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (8)
.agentworkforce/trajectories/completed/2026-06/traj_pmzcmccpttxb.trace.json.agentworkforce/trajectories/completed/2026-06/traj_pmzcmccpttxb/summary.md.agentworkforce/trajectories/completed/2026-06/traj_pmzcmccpttxb/trajectory.json.trajectories/completed/2026-06/traj_4pvrlmqfnzng/trajectory.jsonpackages/sdk/typescript/src/client.test.tspackages/sdk/typescript/src/client.tspackages/sdk/typescript/src/sync.test.tspackages/sdk/typescript/src/sync.ts
✅ Files skipped from review due to trivial changes (4)
- .agentworkforce/trajectories/completed/2026-06/traj_pmzcmccpttxb/summary.md
- .agentworkforce/trajectories/completed/2026-06/traj_pmzcmccpttxb.trace.json
- .agentworkforce/trajectories/completed/2026-06/traj_pmzcmccpttxb/trajectory.json
- .trajectories/completed/2026-06/traj_4pvrlmqfnzng/trajectory.json
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/sdk/typescript/src/client.ts
- packages/sdk/typescript/src/client.test.ts
| eventId?: string; | ||
| path?: string; | ||
| revision?: string; | ||
| contentHash?: string; |
There was a problem hiding this comment.
Validate contentHash type before normalization emits events.
At Line 258, contentHash is forwarded from untrusted WebSocket payload without a runtime type guard. If payload sends a non-string (e.g., object/number), emitted FilesystemEvent.contentHash violates the contract and can break downstream consumers expecting string | undefined.
Suggested fix
@@
if (parsed.ts !== undefined && typeof parsed.ts !== "string") {
this.emit("error", new Error("Invalid WebSocket payload: 'ts' must be a string."));
return;
}
+ if (parsed.contentHash !== undefined && typeof parsed.contentHash !== "string") {
+ this.emit("error", new Error("Invalid WebSocket payload: 'contentHash' must be a string."));
+ return;
+ }
if (parsed.type === "pong") {Also applies to: 258-258
🤖 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/sdk/typescript/src/sync.ts` at line 142, The contentHash field
declared with type annotation at line 142 is being forwarded from an untrusted
WebSocket payload at line 258 without runtime type validation. This can cause
type violations if the payload contains a non-string value (such as an object or
number). Add a runtime type guard before the normalization event is emitted at
line 258 to validate that contentHash is either a string or undefined, ensuring
it conforms to the declared type contract and prevents downstream consumers from
receiving invalid types. Only proceed with emitting the FilesystemEvent if the
contentHash passes validation.
- ops:read in delegated join scopes: a successful /fs/bulk returns an opID this
command then polls at GET /ops/{opId} (requires ops:read). [Codex]
- Redact file body from terminal receipts: clear Content/Encoding before
writing acked/failed receipts and JSON output; pending receipt keeps the body
and is written 0600. [CodeRabbit]
- Keep dispatched op pending on unknown final status: a poll timeout or
transient GET error after an opID is "unknown", not "failed" — record
pending+needsAttention instead of diverging local state with a failed
receipt. Only failed/dead_lettered/canceled write a failed receipt.
[CodeRabbit]
- Re-validate delegated scopes after bootstrap: fail loudly if the minted
bundle omits the required relayfile:fs:write scope. [CodeRabbit]
- resolveWritebackPushPath rejects non-regular files (FIFO/device/socket)
before os.ReadFile. [CodeRabbit]
- joinRemotePath trims a trailing slash on remoteRoot to avoid //double
slashes in the bulk-write path. [CodeRabbit]
- workspace status: use public IncrementalReadNotReadySince as the stuck-event
baseline, max'd with private cursor health; resolve env/agent-relay/catalog
default before legacy credentials so status works in the delegated flow.
[CodeRabbit]
SDK webhook-route findings (Codex/Devin on client.ts) are not applicable: that
code merged separately via #281 and is no longer in this PR's diff.
Tests: receipt redaction/perms/routing, isTerminalWritebackOpStatus, updated
push scope assertion. Full ./cmd/relayfile-cli + ./internal/mountsync pass;
contract check passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* AR-272 add direct writeback push
* AR-272 address review + complete stuck-event spec
Review fixes (Gemini):
- isWritebackDraftPath: use path.Base (slash-separated remote paths) instead
of filepath.Base for Windows correctness; import "path".
- waitForWritebackOperation: accept context.Context with the configured
timeout; poll is now cancellable and tolerates transient errors until the
deadline rather than aborting the push on the first glitch.
Spec completion (AR-272 Parts 2a/2b/3, this repo):
- Part 2a in-cycle stuck-event drain: a read 404 on a provider-layout-alias
path (by-state/by-id/...) is a stale index event, so the cycle skips it
immediately and keeps draining instead of holding the events cursor for the
full read-not-ready TTL and exiting after one event. Canonical (non-alias)
404s keep the conservative retry behavior.
- Part 2b: add `relayfile writeback skip-stuck [WS] [--max N] [--json]`
operator escape hatch that walks the cursor past every read-404 event
immediately (Syncer.SkipStuck) and reports the count skipped.
- Part 3: mount cycle logs a stuck-event drain summary (N skipped; backlog
remains -> use skip-stuck) instead of exiting silently mid-drain.
Part 2c (by-state index emitter) is writer-side and lives in the cloud repo,
out of scope here.
Tests: alias in-cycle drain, skip-stuck consecutive 404 drain, draft-path
slash handling; full ./cmd/relayfile-cli + ./internal/mountsync suites pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* AR-272 address Codex/Devin/CodeRabbit review on writeback push
- ops:read in delegated join scopes: a successful /fs/bulk returns an opID this
command then polls at GET /ops/{opId} (requires ops:read). [Codex]
- Redact file body from terminal receipts: clear Content/Encoding before
writing acked/failed receipts and JSON output; pending receipt keeps the body
and is written 0600. [CodeRabbit]
- Keep dispatched op pending on unknown final status: a poll timeout or
transient GET error after an opID is "unknown", not "failed" — record
pending+needsAttention instead of diverging local state with a failed
receipt. Only failed/dead_lettered/canceled write a failed receipt.
[CodeRabbit]
- Re-validate delegated scopes after bootstrap: fail loudly if the minted
bundle omits the required relayfile:fs:write scope. [CodeRabbit]
- resolveWritebackPushPath rejects non-regular files (FIFO/device/socket)
before os.ReadFile. [CodeRabbit]
- joinRemotePath trims a trailing slash on remoteRoot to avoid //double
slashes in the bulk-write path. [CodeRabbit]
- workspace status: use public IncrementalReadNotReadySince as the stuck-event
baseline, max'd with private cursor health; resolve env/agent-relay/catalog
default before legacy credentials so status works in the delegated flow.
[CodeRabbit]
SDK webhook-route findings (Codex/Devin on client.ts) are not applicable: that
code merged separately via #281 and is no longer in this PR's diff.
Tests: receipt redaction/perms/routing, isTerminalWritebackOpStatus, updated
push scope assertion. Full ./cmd/relayfile-cli + ./internal/mountsync pass;
contract check passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: sync lockfile mount/core deps to 0.8.29
The v0.8.29 release bumped @relayfile/mount-* and @relayfile/core to 0.8.29 in
the workspace package.json files but did not update the root package-lock.json,
which still pinned 0.8.28. Because packages/sdk/typescript is a root workspace
member, `npm ci` resolves against the root lockfile and failed the SDK deps
install — breaking the contract, SDK Typecheck, E2E, and provider-eval jobs
(Go Build/Test were unaffected). Regenerated via `npm install
--package-lock-only`; the diff is the 0.8.28->0.8.29 bump with registry
integrity hashes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Tests