diff --git a/packages/contracts/src/auth.ts b/packages/contracts/src/auth.ts index 8439d12b069..f764f79f2b0 100644 --- a/packages/contracts/src/auth.ts +++ b/packages/contracts/src/auth.ts @@ -109,7 +109,7 @@ export const AuthBootstrapResult = Schema.Struct({ authenticated: Schema.Literal(true), role: AuthSessionRole, sessionMethod: ServerAuthSessionMethod, - expiresAt: Schema.DateTimeUtc, + expiresAt: Schema.DateTimeUtcFromString, }); export type AuthBootstrapResult = typeof AuthBootstrapResult.Type; @@ -117,14 +117,14 @@ export const AuthBearerBootstrapResult = Schema.Struct({ authenticated: Schema.Literal(true), role: AuthSessionRole, sessionMethod: Schema.Literal("bearer-session-token"), - expiresAt: Schema.DateTimeUtc, + expiresAt: Schema.DateTimeUtcFromString, sessionToken: TrimmedNonEmptyString, }); export type AuthBearerBootstrapResult = typeof AuthBearerBootstrapResult.Type; export const AuthWebSocketTokenResult = Schema.Struct({ token: TrimmedNonEmptyString, - expiresAt: Schema.DateTimeUtc, + expiresAt: Schema.DateTimeUtcFromString, }); export type AuthWebSocketTokenResult = typeof AuthWebSocketTokenResult.Type; @@ -132,7 +132,7 @@ export const AuthPairingCredentialResult = Schema.Struct({ id: TrimmedNonEmptyString, credential: TrimmedNonEmptyString, label: Schema.optionalKey(TrimmedNonEmptyString), - expiresAt: Schema.DateTimeUtc, + expiresAt: Schema.DateTimeUtcFromString, }); export type AuthPairingCredentialResult = typeof AuthPairingCredentialResult.Type; @@ -142,8 +142,8 @@ export const AuthPairingLink = Schema.Struct({ role: AuthSessionRole, subject: TrimmedNonEmptyString, label: Schema.optionalKey(TrimmedNonEmptyString), - createdAt: Schema.DateTimeUtc, - expiresAt: Schema.DateTimeUtc, + createdAt: Schema.DateTimeUtcFromString, + expiresAt: Schema.DateTimeUtcFromString, }); export type AuthPairingLink = typeof AuthPairingLink.Type; @@ -172,9 +172,9 @@ export const AuthClientSession = Schema.Struct({ role: AuthSessionRole, method: ServerAuthSessionMethod, client: AuthClientMetadata, - issuedAt: Schema.DateTimeUtc, - expiresAt: Schema.DateTimeUtc, - lastConnectedAt: Schema.NullOr(Schema.DateTimeUtc), + issuedAt: Schema.DateTimeUtcFromString, + expiresAt: Schema.DateTimeUtcFromString, + lastConnectedAt: Schema.NullOr(Schema.DateTimeUtcFromString), connected: Schema.Boolean, current: Schema.Boolean, }); @@ -261,6 +261,6 @@ export const AuthSessionState = Schema.Struct({ auth: ServerAuthDescriptor, role: Schema.optionalKey(AuthSessionRole), sessionMethod: Schema.optionalKey(ServerAuthSessionMethod), - expiresAt: Schema.optionalKey(Schema.DateTimeUtc), + expiresAt: Schema.optionalKey(Schema.DateTimeUtcFromString), }); export type AuthSessionState = typeof AuthSessionState.Type; diff --git a/packages/ssh/src/tunnel.ts b/packages/ssh/src/tunnel.ts index 5ee5c684779..4b25ae1ad7b 100644 --- a/packages/ssh/src/tunnel.ts +++ b/packages/ssh/src/tunnel.ts @@ -322,7 +322,7 @@ function probe() { (response) => { response.resume(); response.once("end", () => { - resolve(response.statusCode >= 200 && response.statusCode < 300); + resolve(true); }); }, ); @@ -895,7 +895,6 @@ export const waitForHttpReady = Effect.fn("ssh/tunnel.waitForHttpReady")(functio }); const readinessClient = client.pipe( - HttpClient.filterStatusOk, HttpClient.transform((effect) => Effect.gen(function* () { attempt += 1; @@ -1212,6 +1211,19 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: }), ), Effect.flatMap(([stderr, exitCode]) => { + // Exit 0 with no stderr means SSH handed off to a ControlMaster mux + // connection. The port forward is still alive via the master process, so + // this is not a failure — let waitForHttpReady determine readiness. + if (exitCode === 0 && stderr.trim().length === 0) { + return Effect.logDebug("ssh.tunnel.process.controlmaster.handoff", { + ...sshTargetLogFields(input.resolvedTarget), + command: tunnelCommand, + pid: child.pid, + localPort: input.localPort, + remotePort: input.remotePort, + httpBaseUrl: input.httpBaseUrl, + }).pipe(Effect.andThen(Effect.never)); + } const error = new SshCommandError({ command: tunnelCommand, exitCode,