From b13cc502646e81c0de0c405d911ca82fe0e7911a Mon Sep 17 00:00:00 2001 From: Proactive Runtime Bot Date: Mon, 7 Sep 2026 12:35:18 +0200 Subject: [PATCH 1/4] fix(deps): override @relayfile/sdk to unblock local relayflows provisioning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @relayflows/core (as currently published, 1.1.4) pins @relayfile/sdk to ^0.8.0, which resolves to 0.8.10 — a version whose createWorkspaceIfNeeded() throws a fatal error on the 404 that relayfile-cloud's real router returns for the deprecated bare POST /v1/workspaces route (it only registers /v1/workspaces/:id/...; workspaces are Durable Objects created implicitly by ID). Any relayflows workflow that declares agent `permissions` — which turns on in-process provisioning — fails locally with "Failed to create workspace : HTTP 404" before doing any real work. relayfile shipped the actual fix upstream (a747865e, published since 0.10.47): a 404 on the collection route is now treated as "nothing to pre-create" instead of fatal. A version-pin fix is up for review in relayflows itself (AgentWorkforce/relayflows#55); this override unblocks local runs against the currently published @relayflows/core in the meantime and can be dropped once that PR ships a new release. Verified: local `relayflows run` of workflows/verify-fleet-daytona.ts (which attaches permissions to every agent) with this override applied gets past the create-workspace step that previously failed with a fatal HTTP 404, advancing to a distinct, later failure unrelated to this fix (a missing RELAYAUTH_JWT_PRIVATE_KEY_PEM/RELAYAUTH_JWT_KID signing-key pair for real relayfile ACL provisioning against production). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Jne2359AFNa6hnYMzxMm2Y --- package-lock.json | 190 +++++++++++++++++++++++++++++++++------------- package.json | 1 + 2 files changed, 140 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c46e52d83..2ae4e4c5c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@agent-relay/monorepo", - "version": "11.5.2", + "version": "11.10.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@agent-relay/monorepo", - "version": "11.5.2", + "version": "11.10.3", "license": "Apache-2.0", "workspaces": [ "packages/*" @@ -4252,6 +4252,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -4269,6 +4270,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -4286,6 +4288,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -4303,6 +4306,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -4320,6 +4324,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -4337,6 +4342,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -4354,6 +4360,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -4371,6 +4378,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -4388,6 +4396,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -4405,6 +4414,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -4422,6 +4432,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -4439,6 +4450,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -4456,6 +4468,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -4473,6 +4486,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -4490,6 +4504,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -4507,6 +4522,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -4524,6 +4540,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -4541,6 +4558,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -4558,6 +4576,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -4575,6 +4594,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -4592,6 +4612,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -4609,6 +4630,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -4626,6 +4648,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -4643,6 +4666,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -4660,6 +4684,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -4677,6 +4702,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -6202,10 +6228,10 @@ } }, "node_modules/@relayfile/core": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@relayfile/core/-/core-0.8.10.tgz", - "integrity": "sha512-fq5607zeDCw7XIB/q1xtNnFqgak9p+MQoqZ4UmreAZ/Fj8ywYv9WYdH5I3/kQX2bMCgQh1KRUjt0FaxPLPJhHg==", - "license": "MIT", + "version": "0.10.54", + "resolved": "https://registry.npmjs.org/@relayfile/core/-/core-0.10.54.tgz", + "integrity": "sha512-psKO/HvXklThe4eFb2lL29lD1RnMbILY8tnkyrD47/oZ309qb2w98PDlZohajgbeO9qwS9VhupoJmDeoDq0pnw==", + "license": "Apache-2.0", "engines": { "node": ">=18" } @@ -6224,6 +6250,58 @@ "node": ">=18" } }, + "node_modules/@relayfile/mount-darwin-arm64": { + "version": "0.10.54", + "resolved": "https://registry.npmjs.org/@relayfile/mount-darwin-arm64/-/mount-darwin-arm64-0.10.54.tgz", + "integrity": "sha512-D0UQOSipYdslvTtT4iFANmZFlotHkJ6Tpifq15IihaXR1ujy6k5DeeViF7C+KvIO7hbcjXs9LbVhGme0411y0w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@relayfile/mount-darwin-x64": { + "version": "0.10.54", + "resolved": "https://registry.npmjs.org/@relayfile/mount-darwin-x64/-/mount-darwin-x64-0.10.54.tgz", + "integrity": "sha512-BQO87V5ex+JxSIsCP/aXcwIdknAEjxpOKKknGK93d2h8/iNbdPC8CXLcTbaGUngO5rVl2mKi8iJgM2xNVswuNg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@relayfile/mount-linux-arm64": { + "version": "0.10.54", + "resolved": "https://registry.npmjs.org/@relayfile/mount-linux-arm64/-/mount-linux-arm64-0.10.54.tgz", + "integrity": "sha512-A7iMaLCjNxpG/dWrf+vs4CDZvAAZYjvd7Bxf3MWIp3NWBV/5cPfojOXgaK/eVcya5PG66x/tdNNTrKAY08ArZw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@relayfile/mount-linux-x64": { + "version": "0.10.54", + "resolved": "https://registry.npmjs.org/@relayfile/mount-linux-x64/-/mount-linux-x64-0.10.54.tgz", + "integrity": "sha512-Om86gou2mcv23FnqmB+uyhyZGV3Mu9pqM6EEDG6iloU2Gsop6pciDwP8z74tRWyycmUDkPAaLDKXChGOEjIXzw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@relayfile/relay-helpers": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/@relayfile/relay-helpers/-/relay-helpers-0.4.6.tgz", @@ -6253,17 +6331,23 @@ } }, "node_modules/@relayfile/sdk": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@relayfile/sdk/-/sdk-0.8.10.tgz", - "integrity": "sha512-16aFuXOv9vAdBz9uymY9QwdrhaahPGczrvw3U8NUp/9B6foyE2gb7sFPtXupKlY/M2hXYrKJ9RA7dubc5sHndw==", - "license": "MIT", + "version": "0.10.54", + "resolved": "https://registry.npmjs.org/@relayfile/sdk/-/sdk-0.10.54.tgz", + "integrity": "sha512-ygWa4te+4Hs9/CDEgXOZAJTeZeEA0JB3ZgxS29BEw0mzAKaYMCWlBMtfWeUx51Ut0h1jJt01ikGYPkWcZ4tjiw==", + "license": "Apache-2.0", "dependencies": { - "@relayfile/core": "0.8.10", + "@relayfile/core": "0.10.54", "ignore": "^7.0.5", "tar": "^7.5.10" }, "engines": { "node": ">=18" + }, + "optionalDependencies": { + "@relayfile/mount-darwin-arm64": "0.10.54", + "@relayfile/mount-darwin-x64": "0.10.54", + "@relayfile/mount-linux-arm64": "0.10.54", + "@relayfile/mount-linux-x64": "0.10.54" } }, "node_modules/@relayflows/browser-primitive": { @@ -8531,6 +8615,7 @@ "version": "0.0.7", "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz", "integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==", + "dev": true, "optional": true, "engines": { "node": ">=10.0.0" @@ -8885,6 +8970,7 @@ "version": "0.0.10", "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "dev": true, "hasInstallScript": true, "optional": true, "dependencies": { @@ -11318,6 +11404,7 @@ "version": "2.27.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.27.0.tgz", "integrity": "sha512-hC+0LidcL3XE4rp1C4H54KujgXKzbfyTngZTwBByQxsOxCEKZT0MPQ4hOKUH2jU1OYstqdDH4onyHPDzcV0XdQ==", + "dev": true, "license": "MIT", "optional": true }, @@ -13506,14 +13593,14 @@ }, "packages/brand": { "name": "@agent-relay/brand", - "version": "11.5.2", + "version": "11.10.3", "engines": { "node": ">=22.0.0" } }, "packages/broker-darwin-arm64": { "name": "@agent-relay/broker-darwin-arm64", - "version": "11.5.2", + "version": "11.10.3", "license": "MIT", "engines": { "node": ">=22.0.0" @@ -13521,7 +13608,7 @@ }, "packages/broker-darwin-x64": { "name": "@agent-relay/broker-darwin-x64", - "version": "11.5.2", + "version": "11.10.3", "license": "MIT", "engines": { "node": ">=22.0.0" @@ -13529,7 +13616,7 @@ }, "packages/broker-linux-arm64": { "name": "@agent-relay/broker-linux-arm64", - "version": "11.5.2", + "version": "11.10.3", "license": "MIT", "engines": { "node": ">=22.0.0" @@ -13537,7 +13624,7 @@ }, "packages/broker-linux-x64": { "name": "@agent-relay/broker-linux-x64", - "version": "11.5.2", + "version": "11.10.3", "license": "MIT", "engines": { "node": ">=22.0.0" @@ -13545,7 +13632,7 @@ }, "packages/broker-win32-x64": { "name": "@agent-relay/broker-win32-x64", - "version": "11.5.2", + "version": "11.10.3", "license": "MIT", "engines": { "node": ">=22.0.0" @@ -13553,16 +13640,17 @@ }, "packages/cli": { "name": "agent-relay", - "version": "11.5.2", + "version": "11.10.3", "license": "Apache-2.0", "dependencies": { - "@agent-relay/cloud": "11.5.2", - "@agent-relay/config": "11.5.2", - "@agent-relay/fleet": "11.5.2", - "@agent-relay/harness-driver": "11.5.2", - "@agent-relay/harnesses": "11.5.2", - "@agent-relay/sdk": "11.5.2", - "@agent-relay/utils": "11.5.2", + "@agent-relay/cloud": "11.10.3", + "@agent-relay/config": "11.10.3", + "@agent-relay/fleet": "11.10.3", + "@agent-relay/harness-driver": "11.10.3", + "@agent-relay/harnesses": "11.10.3", + "@agent-relay/sdk": "11.10.3", + "@agent-relay/session": "11.10.3", + "@agent-relay/utils": "11.10.3", "@modelcontextprotocol/sdk": "^1.23.0", "@relayfile/client": "^0.10.27", "@relayflows/cli": "1.0.1", @@ -13590,9 +13678,9 @@ }, "packages/cloud": { "name": "@agent-relay/cloud", - "version": "11.5.2", + "version": "11.10.3", "dependencies": { - "@agent-relay/config": "11.5.2", + "@agent-relay/config": "11.10.3", "@aws-sdk/client-s3": "3.1020.0", "ignore": "^7.0.5", "tar": "^7.5.21" @@ -13611,7 +13699,7 @@ }, "packages/config": { "name": "@agent-relay/config", - "version": "11.5.2", + "version": "11.10.3", "dependencies": { "zod": "^4.4.3" }, @@ -13626,11 +13714,11 @@ }, "packages/evals": { "name": "@agent-relay/evals", - "version": "11.5.2", + "version": "11.10.3", "license": "Apache-2.0", "dependencies": { - "@agent-relay/harness-driver": "11.5.2", - "@agent-relay/integration-prompts": "11.5.2" + "@agent-relay/harness-driver": "11.10.3", + "@agent-relay/integration-prompts": "11.10.3" }, "engines": { "node": ">=22.0.0" @@ -13638,11 +13726,11 @@ }, "packages/fleet": { "name": "@agent-relay/fleet", - "version": "11.5.2", + "version": "11.10.3", "license": "Apache-2.0", "dependencies": { - "@agent-relay/harness-driver": "11.5.2", - "@agent-relay/harnesses": "11.5.2", + "@agent-relay/harness-driver": "11.10.3", + "@agent-relay/harnesses": "11.10.3", "@relaycast/sdk": "^8.0.7", "ws": "^8.18.3", "zod": "^4.4.3" @@ -13656,10 +13744,10 @@ }, "packages/harness-driver": { "name": "@agent-relay/harness-driver", - "version": "11.5.2", + "version": "11.10.3", "license": "Apache-2.0", "dependencies": { - "@agent-relay/sdk": "11.5.2", + "@agent-relay/sdk": "11.10.3", "ws": "^8.18.3", "zod": "^4.4.3" }, @@ -13667,20 +13755,20 @@ "node": ">=22.0.0" }, "optionalDependencies": { - "@agent-relay/broker-darwin-arm64": "11.5.2", - "@agent-relay/broker-darwin-x64": "11.5.2", - "@agent-relay/broker-linux-arm64": "11.5.2", - "@agent-relay/broker-linux-x64": "11.5.2", - "@agent-relay/broker-win32-x64": "11.5.2" + "@agent-relay/broker-darwin-arm64": "11.10.3", + "@agent-relay/broker-darwin-x64": "11.10.3", + "@agent-relay/broker-linux-arm64": "11.10.3", + "@agent-relay/broker-linux-x64": "11.10.3", + "@agent-relay/broker-win32-x64": "11.10.3" } }, "packages/harnesses": { "name": "@agent-relay/harnesses", - "version": "11.5.2", + "version": "11.10.3", "license": "Apache-2.0", "dependencies": { - "@agent-relay/harness-driver": "11.5.2", - "@agent-relay/sdk": "11.5.2", + "@agent-relay/harness-driver": "11.10.3", + "@agent-relay/sdk": "11.10.3", "@ai-sdk/harness": "1.0.34", "@ai-sdk/harness-claude-code": "1.0.35", "@ai-sdk/harness-codex": "1.0.40", @@ -13694,7 +13782,7 @@ }, "packages/integration-prompts": { "name": "@agent-relay/integration-prompts", - "version": "11.5.2", + "version": "11.10.3", "license": "Apache-2.0", "engines": { "node": ">=22.0.0" @@ -13702,9 +13790,9 @@ }, "packages/policy": { "name": "@agent-relay/policy", - "version": "11.5.2", + "version": "11.10.3", "dependencies": { - "@agent-relay/config": "11.5.2" + "@agent-relay/config": "11.10.3" }, "devDependencies": { "@types/node": "^22.19.3", @@ -13716,7 +13804,7 @@ }, "packages/sdk": { "name": "@agent-relay/sdk", - "version": "11.5.2", + "version": "11.10.3", "dependencies": { "@relaycast/sdk": "^8.0.7", "@relaycast/types": "^8.0.7", @@ -13731,7 +13819,7 @@ }, "packages/session": { "name": "@agent-relay/session", - "version": "11.5.5", + "version": "11.10.3", "license": "Apache-2.0", "dependencies": { "@relaycast/sdk": "^8.0.7" @@ -13747,9 +13835,9 @@ }, "packages/utils": { "name": "@agent-relay/utils", - "version": "11.5.2", + "version": "11.10.3", "dependencies": { - "@agent-relay/config": "11.5.2", + "@agent-relay/config": "11.10.3", "compare-versions": "^6.1.1" }, "devDependencies": { diff --git a/package.json b/package.json index 37890950d5..9e65243488 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ }, "packageManager": "npm@10.5.1", "overrides": { + "@relayfile/sdk": "^0.10.47", "flatted": "^3.4.2", "axios": "^1.18.1", "fast-uri": "^3.1.4", From 3a3828bf38cb35ef470864f31b6105c3a877040a Mon Sep 17 00:00:00 2001 From: Proactive Runtime Bot Date: Mon, 7 Sep 2026 12:46:48 +0200 Subject: [PATCH 2/4] test(pr-proof): add RelayFlow case for the @relayfile/sdk 404 fix Cloud red/green proof for relay#1702: a self-contained mock relayfile-cloud that 404s only the deprecated bare POST /v1/workspaces collection route (matching the real router, which only registers /v1/workspaces/:id/...), then drives the target checkout's own installed createWorkspaceIfNeeded() against it. Verified locally against both arms before pushing: - base (no override, @relayfile/sdk resolves 0.8.10): outcome=bug, signature=relayfile_sdk_404_fatal - head (override applied, resolves 0.10.54): outcome=fixed, signature=relayfile_sdk_404_treated_as_noop Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Jne2359AFNa6hnYMzxMm2Y --- .../case.json | 21 +++ .../1702-relayfile-sdk-workspace-404/run.mjs | 155 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 tests/relayflows/cases/1702-relayfile-sdk-workspace-404/case.json create mode 100644 tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs diff --git a/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/case.json b/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/case.json new file mode 100644 index 0000000000..d8ce695407 --- /dev/null +++ b/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/case.json @@ -0,0 +1,21 @@ +{ + "version": 1, + "id": "1702-relayfile-sdk-workspace-404", + "kind": "bugfix", + "title": "createWorkspaceIfNeeded treats a 404 on the workspace collection route as fatal", + "runner": { + "command": ["node", "tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs"] + }, + "requirements": [], + "timeoutSeconds": 900, + "expected": { + "base": { + "outcome": "bug", + "signature": "relayfile_sdk_404_fatal" + }, + "head": { + "outcome": "fixed", + "signature": "relayfile_sdk_404_treated_as_noop" + } + } +} diff --git a/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs b/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs new file mode 100644 index 0000000000..4a5d714e0e --- /dev/null +++ b/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs @@ -0,0 +1,155 @@ +import { spawnSync } from 'node:child_process'; +import { createServer } from 'node:http'; +import { mkdir, rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const CASE_ID = '1702-relayfile-sdk-workspace-404'; +const INSTALL_TIMEOUT_MS = 10 * 60 * 1000; + +const targetDir = requiredDirectory('RELAY_PR_PROOF_TARGET_DIR'); +const harnessDir = requiredDirectory('RELAY_PR_PROOF_HARNESS_DIR'); +const resultPath = requiredValue('RELAY_PR_PROOF_RESULT_PATH'); +const arm = requiredValue('RELAY_PR_PROOF_ARM'); + +if (arm !== 'base' && arm !== 'head') { + throw new Error(`RELAY_PR_PROOF_ARM must be base or head, received ${JSON.stringify(arm)}.`); +} + +const expectedSha = + arm === 'base' ? process.env.RELAY_PR_PROOF_BASE_SHA : process.env.RELAY_PR_PROOF_HEAD_SHA; +if (!expectedSha) throw new Error(`Missing expected ${arm} SHA.`); +const targetSha = run('git', ['-C', targetDir, 'rev-parse', 'HEAD'], targetDir, 'git rev-parse').stdout.trim(); +if (targetSha !== expectedSha) { + throw new Error(`Target checkout ${targetSha} does not match exact ${arm} SHA ${expectedSha}.`); +} + +const runnerPath = fileURLToPath(import.meta.url); +if (!isWithin(harnessDir, runnerPath)) { + throw new Error('The RelayFlow runner must execute from the exact-head harness checkout.'); +} + +// The bug (relay#1702) is that @relayflows/core pins @relayfile/sdk to a +// version whose createWorkspaceIfNeeded() treats a 404 on the deprecated +// bare `POST /v1/workspaces` collection route as fatal. relayfile-cloud's +// real router never registers that route — only `/v1/workspaces/:id/...`, +// since workspaces are Durable Objects created implicitly by ID — so this +// is a guaranteed failure, not a hypothetical one. Reproducing it faithfully +// only requires the target checkout's OWN resolved @relayfile/sdk exercising +// the exact route shape the real service returns; standing up the real +// relayfile-cloud service is unnecessary and would make the case dependent +// on production availability instead of on the code actually under test. +const server = createServer((req, res) => { + const isBareWorkspacesCollectionPost = + req.method === 'POST' && new URL(req.url, 'http://127.0.0.1').pathname === '/v1/workspaces'; + if (isBareWorkspacesCollectionPost) { + res.writeHead(404, { 'content-type': 'application/json' }); + res.end(JSON.stringify({ code: 'not_found', message: 'Route not found' })); + return; + } + // Any ID-scoped route (the only kind relayfile-cloud actually registers) + // succeeds, matching production. + res.writeHead(204); + res.end(); +}); + +let details; +let outcome; +let signature; + +try { + await new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(0, '127.0.0.1', () => { + server.off('error', reject); + resolve(); + }); + }); + const address = server.address(); + const baseUrl = `http://127.0.0.1:${address.port}`; + + // Fresh, exact-lockfile install of the target checkout so the resolved + // @relayfile/sdk version is whatever that checkout's package.json and + // package-lock.json actually pin — the real thing under test, not an + // assumption about it. + await rm(path.join(targetDir, 'node_modules'), { recursive: true, force: true }); + const install = spawnSync('npm', ['ci'], { + cwd: targetDir, + encoding: 'utf8', + timeout: INSTALL_TIMEOUT_MS, + }); + if (install.error) { + throw new Error(`npm ci could not start: ${install.error.message}`); + } + if (install.status !== 0) { + throw new Error( + `npm ci failed (exit ${install.status ?? 'unknown'}): ${(install.stderr ?? '').slice(-2000)}` + ); + } + + const sdkEntryPath = path.join(targetDir, 'node_modules/@relayfile/sdk/dist/workspace-seeder.js'); + const sdkPackagePath = path.join(targetDir, 'node_modules/@relayfile/sdk/package.json'); + const { default: sdkPackage } = await import(pathToFileURL(sdkPackagePath).href, { + with: { type: 'json' }, + }); + const { createWorkspaceIfNeeded } = await import(pathToFileURL(sdkEntryPath).href); + + let thrown = null; + try { + await createWorkspaceIfNeeded(baseUrl, 'proof-token', 'proof-workspace'); + } catch (error) { + thrown = error; + } + + if (thrown === null) { + outcome = 'fixed'; + signature = 'relayfile_sdk_404_treated_as_noop'; + details = `@relayfile/sdk@${sdkPackage.version}: createWorkspaceIfNeeded() returned normally against a mock relayfile-cloud that 404s the bare collection route, matching the real service.`; + } else if (/HTTP 404/.test(thrown.message ?? '')) { + outcome = 'bug'; + signature = 'relayfile_sdk_404_fatal'; + details = `@relayfile/sdk@${sdkPackage.version}: createWorkspaceIfNeeded() threw on the mock relayfile-cloud's 404 for the bare collection route: ${thrown.message.slice(0, 500)}`; + } else { + throw thrown; + } +} finally { + await new Promise((resolve) => server.close(resolve)); +} + +await mkdir(path.dirname(resultPath), { recursive: true }); +await writeFile( + resultPath, + `${JSON.stringify({ version: 1, caseId: CASE_ID, arm, outcome, signature, details })}\n` +); + +function requiredValue(name) { + const value = process.env[name]?.trim(); + if (!value) throw new Error(`Missing required environment variable ${name}.`); + return value; +} + +function requiredDirectory(name) { + return path.resolve(requiredValue(name)); +} + +function isWithin(directory, candidate) { + const relative = path.relative(directory, candidate); + return ( + relative === '' || + (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)) + ); +} + +function run(command, args, cwd, label) { + const completed = spawnSync(command, args, { cwd, encoding: 'utf8', timeout: INSTALL_TIMEOUT_MS }); + if (completed.error) throw new Error(`${label} could not start: ${completed.error.message}`); + if (completed.status !== 0) { + throw new Error( + `${label} failed with ${ + completed.signal ? `signal ${completed.signal}` : `exit code ${completed.status ?? 'unknown'}` + }: ${(completed.stderr ?? '').slice(-2000)}` + ); + } + return completed; +} From 46dd23296a632016aebef92650e25bdd7b9ebdc7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Sep 2026 10:48:05 +0000 Subject: [PATCH 3/4] style: auto-format with Prettier --- .../cases/1702-relayfile-sdk-workspace-404/run.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs b/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs index 4a5d714e0e..17a7620f2f 100644 --- a/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs +++ b/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs @@ -20,7 +20,12 @@ if (arm !== 'base' && arm !== 'head') { const expectedSha = arm === 'base' ? process.env.RELAY_PR_PROOF_BASE_SHA : process.env.RELAY_PR_PROOF_HEAD_SHA; if (!expectedSha) throw new Error(`Missing expected ${arm} SHA.`); -const targetSha = run('git', ['-C', targetDir, 'rev-parse', 'HEAD'], targetDir, 'git rev-parse').stdout.trim(); +const targetSha = run( + 'git', + ['-C', targetDir, 'rev-parse', 'HEAD'], + targetDir, + 'git rev-parse' +).stdout.trim(); if (targetSha !== expectedSha) { throw new Error(`Target checkout ${targetSha} does not match exact ${arm} SHA ${expectedSha}.`); } From daf935aa0123c8e401cc68c70e3184c7f6d55bc3 Mon Sep 17 00:00:00 2001 From: Proactive Runtime Bot Date: Mon, 7 Sep 2026 13:30:54 +0200 Subject: [PATCH 4/4] fix(deps): address review feedback on relayfile SDK override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Scope the @relayfile/sdk override to @relayflows/core specifically instead of forcing it repo-wide. Unrelated consumers (nested @agent-relay/sdk copies under @agent-assistant/*) declare a wide `>=0.1.2 <1` range for @relayfile/sdk; a blanket override silently forced them onto 0.10.x too, outside what this PR actually validated, even though in practice they already resolve to the same version unscoped (0.10.54 is the highest version satisfying their own range). (cubic P2) - Add a CHANGELOG entry under [Unreleased - Minor]. (cubic P3) - Reuse the case's own run() helper for `npm ci` instead of duplicating spawnSync handling inline — fixes a diagnostic gap where a timed-out install reported "exit unknown" instead of the terminating signal. (coderabbit / cubic P3) - Wrap the createWorkspaceIfNeeded() call in a 15s fail-closed timeout. The SDK call takes no signal/timeout option of its own, so an unexpected stall or retry loop against the mock server would otherwise burn the case's full 900s budget before failing. (cubic P2) Re-verified both arms locally after these changes: base still observes bug/relayfile_sdk_404_fatal, head still observes fixed/relayfile_sdk_404_treated_as_noop. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Jne2359AFNa6hnYMzxMm2Y --- CHANGELOG.md | 1 + package.json | 4 +- .../1702-relayfile-sdk-workspace-404/run.mjs | 39 ++++++++++++------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd5b46baa8..7259a8f28d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - A fleet message the broker cannot deliver to its worker is no longer reported back as handled, so it stays outstanding and can be redelivered. - Fleet deliveries the broker rejects are now logged with a reason and sequence number, so a worker that stops receiving messages can be diagnosed from the broker log. - PTY workers no longer exit when Claude Code's folder-trust dialog appears. Relay selects the affirmative option by its label, so both menu orderings work. +- A relayflow that declares agent `permissions` no longer fails local provisioning with `Failed to create workspace : HTTP 404`. ## [11.10.3] - 2026-09-05 diff --git a/package.json b/package.json index 9e65243488..1562043d89 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,9 @@ }, "packageManager": "npm@10.5.1", "overrides": { - "@relayfile/sdk": "^0.10.47", + "@relayflows/core": { + "@relayfile/sdk": "^0.10.47" + }, "flatted": "^3.4.2", "axios": "^1.18.1", "fast-uri": "^3.1.4", diff --git a/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs b/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs index 17a7620f2f..ed4e65b7b9 100644 --- a/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs +++ b/tests/relayflows/cases/1702-relayfile-sdk-workspace-404/run.mjs @@ -7,6 +7,13 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; const CASE_ID = '1702-relayfile-sdk-workspace-404'; const INSTALL_TIMEOUT_MS = 10 * 60 * 1000; +// createWorkspaceIfNeeded() takes no signal/timeout option of its own — it is +// an opaque call into the target checkout's installed SDK, hitting a +// same-process mock server that answers instantly, so it should never +// legitimately take anywhere near this long. Racing it against a timeout +// keeps a stalled or unexpectedly-retrying call from burning the case's +// entire 900s budget before failing closed with a clear diagnostic. +const WORKSPACE_CALL_TIMEOUT_MS = 15_000; const targetDir = requiredDirectory('RELAY_PR_PROOF_TARGET_DIR'); const harnessDir = requiredDirectory('RELAY_PR_PROOF_HARNESS_DIR'); @@ -79,19 +86,7 @@ try { // package-lock.json actually pin — the real thing under test, not an // assumption about it. await rm(path.join(targetDir, 'node_modules'), { recursive: true, force: true }); - const install = spawnSync('npm', ['ci'], { - cwd: targetDir, - encoding: 'utf8', - timeout: INSTALL_TIMEOUT_MS, - }); - if (install.error) { - throw new Error(`npm ci could not start: ${install.error.message}`); - } - if (install.status !== 0) { - throw new Error( - `npm ci failed (exit ${install.status ?? 'unknown'}): ${(install.stderr ?? '').slice(-2000)}` - ); - } + run('npm', ['ci'], targetDir, 'npm ci'); const sdkEntryPath = path.join(targetDir, 'node_modules/@relayfile/sdk/dist/workspace-seeder.js'); const sdkPackagePath = path.join(targetDir, 'node_modules/@relayfile/sdk/package.json'); @@ -102,7 +97,11 @@ try { let thrown = null; try { - await createWorkspaceIfNeeded(baseUrl, 'proof-token', 'proof-workspace'); + await withTimeout( + createWorkspaceIfNeeded(baseUrl, 'proof-token', 'proof-workspace'), + WORKSPACE_CALL_TIMEOUT_MS, + 'createWorkspaceIfNeeded' + ); } catch (error) { thrown = error; } @@ -146,6 +145,18 @@ function isWithin(directory, candidate) { ); } +async function withTimeout(promise, timeoutMs, label) { + let timer; + const timeout = new Promise((_resolve, reject) => { + timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs); + }); + try { + return await Promise.race([promise, timeout]); + } finally { + clearTimeout(timer); + } +} + function run(command, args, cwd, label) { const completed = spawnSync(command, args, { cwd, encoding: 'utf8', timeout: INSTALL_TIMEOUT_MS }); if (completed.error) throw new Error(`${label} could not start: ${completed.error.message}`);