diff --git a/packages/cli/src/commands/handlers/default.ts b/packages/cli/src/commands/handlers/default.ts index 5521af8fe6..1f8ebe89c4 100644 --- a/packages/cli/src/commands/handlers/default.ts +++ b/packages/cli/src/commands/handlers/default.ts @@ -19,7 +19,7 @@ export default Runtime.handler(Commands, (input) => return yield* Effect.fail(new Error("--server and --standalone cannot be combined")) const transport = yield* Effect.gen(function* () { if (server !== undefined) { - const password = Option.getOrUndefined(yield* Env.password) + const password = yield* Env.password const explicit = { url: server, headers: password diff --git a/packages/cli/src/commands/handlers/serve.ts b/packages/cli/src/commands/handlers/serve.ts index 2b00b297a3..11483189c3 100644 --- a/packages/cli/src/commands/handlers/serve.ts +++ b/packages/cli/src/commands/handlers/serve.ts @@ -27,10 +27,13 @@ export default Runtime.handler( if (input.service) yield* Effect.sync(() => process.chdir(Global.Path.home)) return yield* Effect.scoped( Effect.gen(function* () { - const standalonePassword = Option.getOrUndefined(yield* Env.serverPassword) + const standalonePassword = yield* Env.password // Keep the lease credential out of the environment inherited by any // process this server spawns. - if (input.stdio) delete process.env.OPENCODE_SERVER_PASSWORD + if (input.stdio) { + delete process.env.OPENCODE_PASSWORD + delete process.env.OPENCODE_SERVER_PASSWORD + } const config = input.service ? yield* ServiceConfig.read() : {} const password = input.service ? yield* ServiceConfig.password() diff --git a/packages/cli/src/env.ts b/packages/cli/src/env.ts index 914abac644..6cc76b793f 100644 --- a/packages/cli/src/env.ts +++ b/packages/cli/src/env.ts @@ -4,15 +4,12 @@ import { Config } from "effect" // these instead of touching process.env so the full surface stays visible, // typed, and redacted where secret. -// Client-side password for an explicit --server target. The legacy name is -// still honored; it also remains the variable a standalone child inherits. +// The opencode server password: sent by clients connecting to an explicit +// --server, and adopted by a manually run or standalone server. The legacy +// name is still honored. export const password = Config.redacted("OPENCODE_PASSWORD").pipe( Config.orElse(() => Config.redacted("OPENCODE_SERVER_PASSWORD")), - Config.option, + Config.withDefault(undefined), ) -// Server-side lease password: set by the standalone spawner for its child, -// or preset for a manually managed `opencode serve`. -export const serverPassword = Config.redacted("OPENCODE_SERVER_PASSWORD").pipe(Config.option) - export * as Env from "./env" diff --git a/packages/cli/src/services/standalone.ts b/packages/cli/src/services/standalone.ts index b591a5deb1..285a433b55 100644 --- a/packages/cli/src/services/standalone.ts +++ b/packages/cli/src/services/standalone.ts @@ -15,7 +15,9 @@ function command(password: string) { if (!compiled && entrypoint.length === 0) throw new Error("Failed to resolve CLI entrypoint") return ChildProcess.make(process.execPath, [...entrypoint, "serve", "--stdio", "--port", "0"], { cwd: process.cwd(), - env: { OPENCODE_SERVER_PASSWORD: password }, + // Explicit entry wins over anything inherited, so a user-exported + // OPENCODE_PASSWORD cannot shadow the child's lease credential. + env: { OPENCODE_PASSWORD: password }, extendEnv: true, // The server treats EOF on this pipe as the end of its ownership lease. // The OS closes it even when the TUI is killed before Effect finalizers run.