can1357/oh-my-pi · error
Gateway already attached to ${this.name}
Error message
Gateway already attached to ${this.name} What it means
VibemonSandbox.startGateway attaches a single host-gateway forwarding the sandbox to a local HTTP(S) target. The sandbox caches the gateway in #gateway and throws this error if startGateway is called twice on the same instance, since only one gateway attachment is supported per sandbox.
Source
Thrown at packages/metaharness/src/tb/vmon.ts:302
}
}
process.stdin.close();
return captureProcess(process);
}
/** Read one guest file, returning null when it does not exist. */
async readFile(guestPath: string): Promise<string | null> {
const result = await this.exec(
`if [ -e ${quoteGuestShell(guestPath)} ]; then cat -- ${quoteGuestShell(guestPath)}; else exit 44; fi`,
);
if (result.exitCode === 44) return null;
if (result.exitCode !== 0) throw commandError(`Reading ${this.name}:${guestPath}`, result);
return result.stdout;
}
/** Attach the sandbox host gateway to a local HTTP(S) target. */
async startGateway(localUrl: string): Promise<string> {
if (this.#gateway) throw new Error(`Gateway already attached to ${this.name}`);
const pathname = new URL(localUrl).pathname.replace(/\/+$/, "");
const gateway = await this.#sandbox.hostGateway(localUrl);
this.#gateway = gateway;
return `${gateway.url.replace(/\/+$/, "")}${pathname}`;
}
/** Build an RpcClient launcher backed by a streaming Vibemon exec. */
rpcTransport(entrypoint: string, timeoutSec: number): (agentArgs: string[]) => Promise<RpcAgentProcess> {
return async agentArgs => {
const process = await this.#sandbox.exec([entrypoint, ...agentArgs], {
env: { ...this.#env, HOME: this.home },
workdir: this.workdir,
timeout: timeoutSec,
});
return adaptRpcProcess(process);
};
}
View on GitHub (pinned to 9690622007)
Solutions
- Call startGateway once per sandbox instance and reuse the returned URL
- Create a fresh sandbox instance if you need to re-attach the gateway
- Track attachment in your orchestration layer and guard repeated calls (this error is that guard surfacing)
Example fix
// before await vm.startGateway(url); await vm.startGateway(url); // throws // after const gatewayUrl = gatewayUrlCache.get(vm) ?? await vm.startGateway(url);
Defensive patterns
Strategy: validation
Validate before calling
let gatewayUrl: string | undefined;
async function ensureGateway(vm: VibemonSandbox, localUrl: string): Promise<string> {
return (gatewayUrl ??= await vm.startGateway(localUrl));
} Try / catch
try {
return await vm.startGateway(localUrl);
} catch (err) {
if (String(err.message).startsWith("Gateway already attached to")) {
return retrieveExistingGatewayUrl(vm); // reuse instead of failing
}
throw err;
} Prevention
- Treat each sandbox as single-use: one gateway attachment per instance
- Cache the gateway URL at setup time and pass it down instead of re-calling
- Recreate the sandbox when a new gateway target is needed
When it happens
Trigger: Calling startGateway a second time on the same VibemonSandbox instance — e.g. runTrial invoked twice against one VM, or retry logic re-calling startGateway after a partial failure.
Common situations: Reusing a cached sandbox object across trial runs; retry wrappers that don't recreate the sandbox; calling startGateway in both setup code and the trial runner.
Related errors
- directory stack is empty
- No messages to continue from
- Cannot continue from message role: assistant
- Cursor blob not found
- Not a git repository
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d7d3bd20659c647a.
Report an issue: GitHub.