fix: Tweaks on codespace opencode setup (no-changelog) (#39488)

This commit is contained in:
Svetoslav Dekov
2026-09-24 11:55:54 +00:00
committed by GitHub
parent d13b63054b
commit 772d420dc4
7 changed files with 46 additions and 10 deletions
+7 -3
View File
@@ -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-<name>`, branch
`session/<name>`), so parallel agents never touch each other's tree. Builds
`session/<name>`), 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@<version>
npm install -g opencode-ai@<version>
```
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 <name>` restarts the codespace (~30–60 s); run
@@ -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/);
@@ -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/],
+6 -2
View File
@@ -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] },
);
@@ -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' });
+1 -1
View File
@@ -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) =>
+4 -1
View File
@@ -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('; ');