Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions apps/cli/src/commands/_cloud-agent-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { makeProgressReporter } from '../lib/progress.js';
import { printLaunchRecap } from '../lib/launch-recap.js';
import { buildPromptArgs } from '../lib/queue/build-prompt-args.js';
import { buildResyncWarning } from '../lib/resync-warning.js';
import { cloudAgentAttach } from './_cloud-attach.js';
import { cloudAgentAttach, cloudAgentStartDetached } from './_cloud-attach.js';

export interface CloudAgentCreateArgs {
/** Pre-resolved provider (from `providerForCreate`). */
Expand All @@ -42,11 +42,11 @@ export interface CloudAgentCreateArgs {
verbose?: boolean;
/** Where to open the attached session; forwarded to `cloudAgentAttach`. */
openIn?: AttachOpenIn;
/** When `false`, create the cloud box and skip the agent attach (background
* mode). Defaults to `true`. On cloud providers the agent's tmux session is
* created lazily by `cloudAgentAttach`; with `attach: false` the session
* isn't started yet — a later `agentbox <agent> attach <box>` starts it on
* first attach. */
/** When `false`, create the cloud box and start the agent in a detached tmux
* session (background mode) but don't attach the host terminal — mirrors the
* docker path, where the session is always created and only the terminal
* attach is skipped. A later `agentbox <agent> attach <box>` finds the
* running session and attaches to it. Defaults to `true`. */
attach?: boolean;
/**
* Hook fired AFTER the box is provisioned and BEFORE the agent attach starts
Expand Down Expand Up @@ -118,6 +118,15 @@ export async function cloudAgentCreate(args: CloudAgentCreateArgs): Promise<void
attaching: args.attach !== false,
});
if (args.attach === false) {
// Background mode: start the agent in a detached tmux session (and verify
// it stayed up) without attaching the host terminal — matches docker,
// where the session is always created and only the attach is skipped.
await cloudAgentStartDetached({
box: result.record,
binary: args.binary,
sessionName: args.sessionName,
extraArgs,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detached start skips resume args

Medium Severity

New --no-attach cloud paths call cloudAgentStartDetached with raw extraArgs only. The attach path in cloudAgentAttach still adds agentResumeArgs when args are empty for claude/codex, so background create/start can launch a fresh agent instead of resuming the box’s recorded session (e.g. after checkpoint restore or claude start with no post--- args).

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 512397c. Configure here.

return;
}
await cloudAgentAttach({
Expand Down
15 changes: 14 additions & 1 deletion apps/cli/src/commands/_cloud-attach.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,20 @@ export async function cloudAgentStartDetached(args: {
if (state !== 'running') {
box = await provider.start(box);
}
const command = buildCloudAttachInnerCommand(args.binary, args.extraArgs);
// With no user args, resume the box's recorded session instead of launching
// fresh — same as the interactive `cloudAgentAttach` path. Matters for a
// background `agentbox <agent> start <box> --no-attach` (and idle-resumed
// creates): the box already has a claude/codex session to reopen. The `-i`
// queue path always seeds a prompt, so extraArgs is non-empty and this no-ops.
let extraArgs = args.extraArgs;
if (
(!extraArgs || extraArgs.length === 0) &&
(args.binary === 'claude' || args.binary === 'codex')
) {
const resume = await agentResumeArgs(provider, box, args.binary);
if (resume) extraArgs = resume;
}
const command = buildCloudAttachInnerCommand(args.binary, extraArgs);
const { exitCode, stderr } = await startDetachedSession(
provider,
box,
Expand Down
23 changes: 15 additions & 8 deletions apps/cli/src/commands/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import {
NO_ATTACH_HELP,
resolveAttachInOption,
} from './_attach-in.js';
import { cloudAgentAttach } from './_cloud-attach.js';
import { cloudAgentAttach, cloudAgentStartDetached } from './_cloud-attach.js';
import { cloudAgentCreate } from './_cloud-agent-create.js';
import { runCarryGate, runQueuedCarryGate } from '../lib/carry-gate.js';
import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js';
Expand Down Expand Up @@ -1410,12 +1410,6 @@ const claudeStartCommand = new Command('start')
}
}
if ((box.provider ?? 'docker') !== 'docker') {
if (opts.attach === false) {
outro(
`--no-attach: cloud agent sessions are started lazily on attach. Run: agentbox claude attach ${reattachRef(box)}`,
);
return;
}
const cfg = await loadEffectiveConfig(box.workspacePath, {
cliOverrides: {
...(attachIn ? { attach: { openIn: attachIn } } : {}),
Expand All @@ -1438,10 +1432,23 @@ const claudeStartCommand = new Command('start')
throw err;
}
}
const sessionName = opts.sessionName ?? 'claude';
if (opts.attach === false) {
// Background mode: start the detached session (matches docker) instead
// of deferring the agent until the next attach.
await cloudAgentStartDetached({
box,
binary: 'claude',
sessionName,
extraArgs: effectiveClaudeArgs,
});
outro(`--no-attach: claude started in background. Attach: agentbox claude attach ${reattachRef(box)}`);
return;
}
await cloudAgentAttach({
box,
binary: 'claude',
sessionName: opts.sessionName ?? 'claude',
sessionName,
mode: 'claude',
extraArgs: effectiveClaudeArgs,
openIn: hostAwareOpenIn(cfg),
Expand Down
23 changes: 15 additions & 8 deletions apps/cli/src/commands/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import {
NO_ATTACH_HELP,
resolveAttachInOption,
} from './_attach-in.js';
import { cloudAgentAttach } from './_cloud-attach.js';
import { cloudAgentAttach, cloudAgentStartDetached } from './_cloud-attach.js';
import { cloudAgentCreate } from './_cloud-agent-create.js';
import { runCarryGate, runQueuedCarryGate } from '../lib/carry-gate.js';
import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js';
Expand Down Expand Up @@ -1044,12 +1044,6 @@ const codexStartCommand = new Command('start')
}
}
if ((box.provider ?? 'docker') !== 'docker') {
if (opts.attach === false) {
outro(
`--no-attach: cloud agent sessions are started lazily on attach. Run: agentbox codex attach ${reattachRef(box)}`,
);
return;
}
const cfg = await loadEffectiveConfig(box.workspacePath, {
cliOverrides: {
...(attachIn ? { attach: { openIn: attachIn } } : {}),
Expand All @@ -1072,10 +1066,23 @@ const codexStartCommand = new Command('start')
throw err;
}
}
const sessionName = opts.sessionName ?? 'codex';
if (opts.attach === false) {
// Background mode: start the detached session (matches docker) instead
// of deferring the agent until the next attach.
await cloudAgentStartDetached({
box,
binary: 'codex',
sessionName,
extraArgs: effectiveCodexArgs,
});
outro(`--no-attach: codex started in background. Attach: agentbox codex attach ${reattachRef(box)}`);
return;
}
await cloudAgentAttach({
box,
binary: 'codex',
sessionName: opts.sessionName ?? 'codex',
sessionName,
mode: 'codex',
extraArgs: effectiveCodexArgs,
openIn: hostAwareOpenIn(cfg),
Expand Down
21 changes: 15 additions & 6 deletions apps/cli/src/commands/opencode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import {
NO_ATTACH_HELP,
resolveAttachInOption,
} from './_attach-in.js';
import { cloudAgentAttach } from './_cloud-attach.js';
import { cloudAgentAttach, cloudAgentStartDetached } from './_cloud-attach.js';
import { cloudAgentCreate } from './_cloud-agent-create.js';
import { runCarryGate, runQueuedCarryGate } from '../lib/carry-gate.js';
import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js';
Expand Down Expand Up @@ -886,19 +886,28 @@ const opencodeStartCommand = new Command('start')
}
}
if ((box.provider ?? 'docker') !== 'docker') {
const cfg = await loadEffectiveConfig(box.workspacePath, {
cliOverrides: attachIn ? { attach: { openIn: attachIn } } : {},
});
const sessionName = opts.sessionName ?? 'opencode';
if (opts.attach === false) {
// Background mode: start the detached session (matches docker) instead
// of deferring the agent until the next attach.
await cloudAgentStartDetached({
box,
binary: 'opencode',
sessionName,
extraArgs: effectiveOpencodeArgs,
});
outro(
`--no-attach: cloud agent sessions are started lazily on attach. Run: agentbox opencode attach ${reattachRef(box)}`,
`--no-attach: opencode started in background. Attach: agentbox opencode attach ${reattachRef(box)}`,
);
return;
}
const cfg = await loadEffectiveConfig(box.workspacePath, {
cliOverrides: attachIn ? { attach: { openIn: attachIn } } : {},
});
await cloudAgentAttach({
box,
binary: 'opencode',
sessionName: opts.sessionName ?? 'opencode',
sessionName,
mode: 'opencode',
extraArgs: effectiveOpencodeArgs,
openIn: hostAwareOpenIn(cfg),
Expand Down
Loading