From 772d420dc4ab08fa2d1af5ffb874eeb74f32a2b9 Mon Sep 17 00:00:00 2001 From: Svetoslav Dekov Date: Thu, 24 Sep 2026 11:55:54 +0000 Subject: [PATCH] fix: Tweaks on codespace opencode setup (no-changelog) (#39488) --- .devcontainer/codespaces/README.md | 10 +++++++--- .../codespaces/cloud-session.test.mjs | 5 ++++- .../codespaces/opencode-client.test.mjs | 6 +++++- .devcontainer/codespaces/opencode-server.mjs | 8 ++++++-- .../codespaces/opencode-server.test.mjs | 20 ++++++++++++++++++- scripts/cloud-session-opencode.mjs | 2 +- scripts/cloud-session.mjs | 5 ++++- 7 files changed, 46 insertions(+), 10 deletions(-) diff --git a/.devcontainer/codespaces/README.md b/.devcontainer/codespaces/README.md index 91a51a2b5be..3bd4ac8286f 100644 --- a/.devcontainer/codespaces/README.md +++ b/.devcontainer/codespaces/README.md @@ -46,7 +46,8 @@ create an 8-core Codespace by default. terminal's own text selection, hold **Shift** and drag. - **Reattach** by running the same session command from any machine. - Each named session gets its own worktree (`/workspaces/wt-`, branch - `session/`), so parallel agents never touch each other's tree. Builds + `session/`), so parallel agents never touch each other's tree. A new + branch starts from the latest `origin/master`. Builds in fresh worktrees are cache-hits via a shared turbo cache. - First codespace creation takes ~20 min uncached (image + full build). After that, sessions attach instantly; new worktrees cost a `pnpm install` (~10 s: the @@ -84,9 +85,12 @@ For the TUI, install the same OpenCode version as the remote server. The for new images: ```bash -pnpm add --global opencode-ai@ +npm install -g opencode-ai@ ``` +Use npm, not pnpm. The package gets its binary from a `postinstall` script. +pnpm does not run that script by default. + The launcher checks both versions. It reports a mismatch with both version numbers before it opens the TUI. An existing Codespace can have a different version. Install that version locally, or use `--web`. Browser mode does not @@ -423,7 +427,7 @@ partial mapping breaks it. | Event | Running processes | Disk (checkout, worktrees, chat history) | |---|---|---| | Detach / close laptop / network drop | ✅ keep running | ✅ | -| Stop, or idle timeout (default 30 min, max 4 h) | ❌ killed | ✅ | +| Stop, or idle timeout (2 h for a codespace that `pnpm session` creates, max 4 h) | ❌ killed | ✅ | | Delete (`pnpm session rm`) | ❌ | ❌ (push your branches first) | After a stop, `pnpm session ` restarts the codespace (~30–60 s); run diff --git a/.devcontainer/codespaces/cloud-session.test.mjs b/.devcontainer/codespaces/cloud-session.test.mjs index ac41a012279..71324b209a5 100644 --- a/.devcontainer/codespaces/cloud-session.test.mjs +++ b/.devcontainer/codespaces/cloud-session.test.mjs @@ -101,7 +101,10 @@ test('starts a named OpenCode session in a worktree', () => { assert.match(command, /unset AGENT_WORKER_TOKEN N8N_DEQUEUE_URL SLACK_BOT_TOKEN/); assert.match(command, /OPENCODE_CONFIG_CONTENT/); assert.match(command, /export N8N_AGENT_RUNTIME=sandbox; unset N8N_AGENT_PROFILE/); - assert.match(command, /git -C \/workspaces\/n8n worktree add "\/workspaces\/wt-fix-flaky"/); + assert.match( + command, + /git -C \/workspaces\/n8n fetch origin master; git -C \/workspaces\/n8n worktree add --no-track -b "session\/fix-flaky" "\/workspaces\/wt-fix-flaky" origin\/master/, + ); assert.match(command, /cd "\/workspaces\/wt-fix-flaky" && opencode --auto --model test/); assert.doesNotMatch(command, /unset OPENROUTER_API_KEY/); assert.doesNotMatch(command, /claude plugin/); diff --git a/.devcontainer/codespaces/opencode-client.test.mjs b/.devcontainer/codespaces/opencode-client.test.mjs index be700f17411..e45fe33b35a 100644 --- a/.devcontainer/codespaces/opencode-client.test.mjs +++ b/.devcontainer/codespaces/opencode-client.test.mjs @@ -201,7 +201,11 @@ for (const signal of ['SIGINT', 'SIGHUP']) { } for (const [label, env, message] of [ - ['a version mismatch', { TEST_SERVER_VERSION: '1.14.22' }, /versions differ/], + [ + 'a version mismatch', + { TEST_SERVER_VERSION: '1.14.22' }, + /versions differ.*npm install -g opencode-ai@1\.14\.22/, + ], ['an authentication failure', { TEST_UNAUTHORIZED: '1' }, /health check failed \(401\)/], ['a tunnel startup failure', { TEST_TUNNEL_FAIL: '1' }, /SSH tunnel closed/], ['a remote preparation failure', { TEST_BOOTSTRAP_FAIL: '1' }, /Could not prepare/], diff --git a/.devcontainer/codespaces/opencode-server.mjs b/.devcontainer/codespaces/opencode-server.mjs index f89cf11b33e..7d109899929 100644 --- a/.devcontainer/codespaces/opencode-server.mjs +++ b/.devcontainer/codespaces/opencode-server.mjs @@ -132,6 +132,10 @@ function prepareWorkspace({ name, directory, mainDirectory, stateDir }) { '--quiet', `refs/heads/${branch}`, ]).status === 0; + if (!exists) + spawnSync('git', ['-C', mainDirectory, 'fetch', 'origin', 'master'], { + stdio: ['ignore', 2, 2], + }); run( 'git', [ @@ -139,9 +143,9 @@ function prepareWorkspace({ name, directory, mainDirectory, stateDir }) { mainDirectory, 'worktree', 'add', - ...(exists ? [] : ['-b', branch]), + ...(exists ? [] : ['--no-track', '-b', branch]), directory, - ...(exists ? [branch] : []), + exists ? branch : 'origin/master', ], { stdio: ['ignore', 2, 2] }, ); diff --git a/.devcontainer/codespaces/opencode-server.test.mjs b/.devcontainer/codespaces/opencode-server.test.mjs index d1ea4df283b..a86713c97a5 100644 --- a/.devcontainer/codespaces/opencode-server.test.mjs +++ b/.devcontainer/codespaces/opencode-server.test.mjs @@ -40,7 +40,8 @@ function fixture(t) { 'git', ` if (args.includes('show-ref')) process.exit(fs.existsSync(file('branch-exists')) ? 0 : 1); -fs.mkdirSync(args.includes('-b') ? args.at(-1) : args.at(-2), { recursive: true }); +if (args.includes('fetch')) process.exit(0); +fs.mkdirSync(args.at(-2), { recursive: true }); `, ); bin( @@ -151,6 +152,23 @@ test( const f = fixture(t); const first = await f.prepare({ name: 'fix-flaky' }); assert.equal(first.directory, join(f.dir, 'wt-fix-flaky')); + const git = f.commands().filter((entry) => entry.command === 'git'); + assert.deepEqual( + git.map((entry) => entry.args.slice(2)), + [ + ['show-ref', '--verify', '--quiet', 'refs/heads/session/fix-flaky'], + ['fetch', 'origin', 'master'], + [ + 'worktree', + 'add', + '--no-track', + '-b', + 'session/fix-flaky', + first.directory, + 'origin/master', + ], + ], + ); assert.equal(first.sessionID, 'ses_1'); assert.deepEqual(await f.prepare({ name: 'fix-flaky' }), first); const second = await f.prepare({ name: 'another-task' }); diff --git a/scripts/cloud-session-opencode.mjs b/scripts/cloud-session-opencode.mjs index c5182bde5a5..465cf21c49b 100644 --- a/scripts/cloud-session-opencode.mjs +++ b/scripts/cloud-session-opencode.mjs @@ -220,7 +220,7 @@ export async function connectOpenCode(options, ensureCodespace) { const health = await waitForTunnel(url, state.password, tunnel, controller.signal); if (version && health.version !== version) { throw new Error( - `OpenCode versions differ: local ${version}, server ${health.version}. Install the matching local version, or use --web or --legacy. The remote server is still running.`, + `OpenCode versions differ: local ${version}, server ${health.version}. Install the matching version with \`npm install -g opencode-ai@${health.version}\`, or use --web or --legacy. The remote server is still running.`, ); } const stopped = new Promise((resolve) => diff --git a/scripts/cloud-session.mjs b/scripts/cloud-session.mjs index e84e840e686..b1b1092e2e6 100644 --- a/scripts/cloud-session.mjs +++ b/scripts/cloud-session.mjs @@ -63,6 +63,8 @@ function ensureCodespace() { DEVCONTAINER, '-m', MACHINE, + '--idle-timeout', + '2h', ); if (status !== 0) { console.error('Codespace create failed. Authorize the permissions prompt above, then retry.'); @@ -118,7 +120,8 @@ function remoteCommand(session, launcher, extraArgs) { return [ prelude, `if [ ! -d "${wt}" ]; then echo "Setting up worktree ${wt}…"`, - `git -C /workspaces/n8n worktree add "${wt}" -b "${branch}" 2>/dev/null || git -C /workspaces/n8n worktree add "${wt}" "${branch}"`, + `git -C /workspaces/n8n fetch origin master`, + `git -C /workspaces/n8n worktree add --no-track -b "${branch}" "${wt}" origin/master 2>/dev/null || git -C /workspaces/n8n worktree add "${wt}" "${branch}"`, `(cd "${wt}" && pnpm install); fi`, `cd "${wt}" && ${command}`, ].join('; ');