Skip to content

Commit b71b2b8

Browse files
PR Batch Testerclaude
andcommitted
fix(server): Swift TestFlight clients can read server settings again
#26238 removed the server.getSettings RPC because nothing in the tree called it, but installed Swift TestFlight builds still request it on connect and from the composer, so they now fail with "Unknown request tag: server.getSettings". Restore the RPC with its original payload, result, errors, read scope and redacted handler. Fixes #26446 Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 0160c44 commit b71b2b8

5 files changed

Lines changed: 90 additions & 0 deletions

File tree

‎apps/server/src/auth/RpcAuthorization.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export const RPC_REQUIRED_SCOPES = {
8383
[WS_METHODS.serverRemoveKeybinding]: AuthSettingsWriteScope,
8484
[WS_METHODS.serverGetStorageCleanupReport]: AuthOrchestrationReadScope,
8585
[WS_METHODS.serverGetDatabaseStorage]: AuthOrchestrationReadScope,
86+
[WS_METHODS.serverGetSettings]: AuthOrchestrationReadScope,
8687
[WS_METHODS.peerLinksList]: AuthAccessReadScope,
8788
[WS_METHODS.serverUpdateSettings]: AuthSettingsWriteScope,
8889
[WS_METHODS.serverSearchAcpRegistry]: AuthOrchestrationReadScope,

‎apps/server/src/observability/RpcInstrumentation.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const RPC_AGGREGATES = {
6060
[WS_METHODS.peerLinksLink]: "peerLinks",
6161
[WS_METHODS.peerLinksUnlink]: "peerLinks",
6262
[WS_METHODS.peerLinksAnswerRequest]: "peerLinks",
63+
[WS_METHODS.serverGetSettings]: "server",
6364
[WS_METHODS.serverUpdateSettings]: "server",
6465
[WS_METHODS.serverSearchAcpRegistry]: "server",
6566
[WS_METHODS.serverPrepareAcpRegistryAgent]: "server",

‎apps/server/src/ws.test.ts‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as NodeServices from "@effect/platform-node/NodeServices";
22
import { assert, it } from "@effect/vitest";
33
import {
4+
AuthOrchestrationReadScope,
45
AuthSessionId,
56
EnvironmentId,
67
ProjectId,
@@ -9,6 +10,7 @@ import {
910
ProviderInstanceId,
1011
WS_METHODS,
1112
WsServerRefreshProvidersRpc,
13+
WsRpcGroup,
1214
WsSubscribeServerConfigRpc,
1315
type OrchestrationV2StoredEvent,
1416
type OrchestrationV2ThreadShell,
@@ -19,6 +21,7 @@ import {
1921
} from "@t3tools/contracts";
2022
import * as Deferred from "effect/Deferred";
2123
import * as DateTime from "effect/DateTime";
24+
import * as Exit from "effect/Exit";
2225
import * as Effect from "effect/Effect";
2326
import * as Layer from "effect/Layer";
2427
import * as PubSub from "effect/PubSub";
@@ -29,6 +32,7 @@ import { Headers } from "effect/http";
2932
import { RpcGroup, RpcMessage, RpcServer } from "effect/rpc";
3033
import * as SqlClient from "effect/sql/SqlClient";
3134

35+
import * as RpcAuthorization from "./auth/RpcAuthorization.ts";
3236
import * as PreviewAutomationBroker from "./mcp/PreviewAutomationBroker.ts";
3337
import * as ServerBrowser from "./preview/ServerBrowser.ts";
3438
import * as AcpRegistryRuntimeCoordinator from "@t3tools/provider-acp-registry/server/AcpRegistryRuntimeCoordinator";
@@ -869,6 +873,79 @@ it.effect("on-demand clients get provider config without workspace snapshots", (
869873
),
870874
);
871875

876+
it.effect("server.getSettings returns redacted settings to installed native clients", () =>
877+
Effect.gen(function* () {
878+
const settings = yield* ServerSettings.ServerSettingsService;
879+
const saved = yield* settings.updateSettings({
880+
defaultModelSelection: {
881+
instanceId: ProviderInstanceId.make("claudeAgent"),
882+
model: "claude-haiku-4-5",
883+
},
884+
sourceControlHosts: { gitcafe: { token: "gitcafe-secret" } },
885+
});
886+
const exit = yield* Deferred.make<Exit.Exit<unknown, unknown>>();
887+
const server = yield* RpcServer.makeNoSerialization(WsRpcGroup, {
888+
...WS_RPC_SERVER_OPTIONS,
889+
onFromServer: (response) =>
890+
response._tag === "Exit" ? Deferred.succeed(exit, response.exit) : Effect.void,
891+
}).pipe(
892+
Effect.provide(
893+
layerWsRpc(
894+
{
895+
sessionId: AuthSessionId.make("settings-test"),
896+
subject: "settings-test",
897+
method: "browser-session-cookie",
898+
scopes: [],
899+
},
900+
{},
901+
yield* ServerBrowser.ServerBrowser,
902+
).pipe(
903+
Layer.provide(
904+
Layer.mergeAll(
905+
layerUnusedConfigRpcServices,
906+
Layer.mock(ExternalLauncher.ExternalLauncher)({}),
907+
Layer.mock(ProviderRegistry.ProviderRegistry)({}),
908+
Layer.mock(UsageLimitSources.UsageLimitSources)({}),
909+
Layer.mock(RemoteOpenTargets.RemoteOpenTargets)({}),
910+
Layer.mock(DirectEndpoints.DirectEndpoints)({}),
911+
Layer.mock(ManagedProjectFolders.ManagedProjectFolders)({
912+
scratchRoot: Effect.succeedNone,
913+
namedProjectsRoot: process.cwd(),
914+
}),
915+
Layer.mock(ServerEnvironment.ServerEnvironment)({}),
916+
Layer.mock(EnvironmentAuth.EnvironmentAuth)({}),
917+
),
918+
),
919+
),
920+
),
921+
);
922+
// Swift TestFlight builds send this tag on connect and from the composer.
923+
yield* server.write(0, {
924+
_tag: "Request",
925+
id: RpcMessage.RequestId("settings"),
926+
tag: "server.getSettings",
927+
payload: {},
928+
headers: Headers.empty,
929+
});
930+
const result = yield* Deferred.await(exit);
931+
if (!Exit.isSuccess(result)) return yield* Effect.die(result.cause);
932+
const value = result.value as typeof saved;
933+
assert.deepEqual(value, ServerSettings.redactServerSettingsForClient(saved));
934+
assert.deepEqual(value.defaultModelSelection, saved.defaultModelSelection);
935+
assert.notInclude(JSON.stringify(value), "gitcafe-secret");
936+
}).pipe(
937+
Effect.provide(
938+
Layer.mergeAll(
939+
Layer.fresh(layerConfigSources),
940+
Layer.mock(PreviewAutomationBroker.PreviewAutomationBroker)({}),
941+
Layer.mock(ServerBrowser.ServerBrowser)({}),
942+
RpcAuthorization.layer([AuthOrchestrationReadScope]),
943+
),
944+
),
945+
Effect.scoped,
946+
),
947+
);
948+
872949
// Compaction keeps the oldest and newest event per message, so a cursor older
873950
// than the deleted rows still replays each message where the client would
874951
// first have seen it (reducers append on first sight) and reaches the same

‎apps/server/src/ws.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2496,6 +2496,8 @@ export const layerWsRpc = (
24962496
Effect.annotateCurrentSpan({ "orchestration_v2.thread_id": input.threadId }).pipe(
24972497
Effect.andThen(peerLinkRequests.answer(input)),
24982498
),
2499+
[WS_METHODS.serverGetSettings]: (_input) =>
2500+
serverSettings.getSettings.pipe(Effect.map(ServerSettings.redactServerSettingsForClient)),
24992501
[WS_METHODS.serverUpdateSettings]:
25002502
/* Request instrumentation already owns the span; keep this helper in that context. */
25012503
Effect.fnUntraced(function* ({ patch, providerInstanceMutation }) {

‎packages/contracts/src/rpc.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ export const WS_METHODS = {
495495
peerLinksLink: "peerLinks.link",
496496
peerLinksUnlink: "peerLinks.unlink",
497497
peerLinksAnswerRequest: "peerLinks.answerRequest",
498+
serverGetSettings: "server.getSettings",
498499
serverUpdateSettings: "server.updateSettings",
499500
serverDiscoverSourceControl: "server.discoverSourceControl",
500501
serverSearchAcpRegistry: "server.searchAcpRegistry",
@@ -793,6 +794,13 @@ const WsPeerLinksAnswerRequestRpc = Rpc.make(WS_METHODS.peerLinksAnswerRequest,
793794
error: Schema.Union([PeerLinkRequestError, EnvironmentAuthorizationError]),
794795
});
795796

797+
// No in-tree caller; installed Swift TestFlight builds still request it.
798+
const WsServerGetSettingsRpc = Rpc.make(WS_METHODS.serverGetSettings, {
799+
payload: Schema.Struct({}),
800+
success: ServerSettings,
801+
error: Schema.Union([ServerSettingsError, EnvironmentAuthorizationError]),
802+
});
803+
796804
const WsServerUpdateSettingsRpc = Rpc.make(WS_METHODS.serverUpdateSettings, {
797805
payload: Schema.Struct({
798806
patch: ServerSettingsPatch,
@@ -1941,6 +1949,7 @@ export const WsRpcGroup = RpcGroup.make(
19411949
WsServerRunStorageCleanupRpc,
19421950
WsServerGetStorageCleanupReportRpc,
19431951
WsServerGetDatabaseStorageRpc,
1952+
WsServerGetSettingsRpc,
19441953
WsServerUpdateSettingsRpc,
19451954
WsPeerLinksListRpc,
19461955
WsPeerLinksLinkRpc,

0 commit comments

Comments
 (0)