From fa73c6afb06aec293c538d92fcc9f945050c66e2 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Thu, 22 Oct 2026 04:02:10 -0700 Subject: [PATCH 1/6] feat: hand off hosted agents to local CLI Co-authored-by: quire Agent-Model: codex/gpt-5.6-luna/xhigh --- backend/services/nativeRuntimeService.ts | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/backend/services/nativeRuntimeService.ts b/backend/services/nativeRuntimeService.ts index d906ed14f0..95e4c71618 100644 --- a/backend/services/nativeRuntimeService.ts +++ b/backend/services/nativeRuntimeService.ts @@ -859,6 +859,51 @@ async function dispatchTool( return { content: { ok: true, posted: true, result } }; } + case 'commonly_offer_local_agent': { + const adapter = String(args.adapter || '') as ByoHandoffAdapter; + let owner: { username?: string } | null = null; + if (ctx.installation?.installedBy) { + try { + owner = await (require('../models/User')).findById(ctx.installation.installedBy) + .select('username').lean() as { username?: string } | null; + } catch { + // A username is only a display-name seed. The handoff remains + // actionable with the stable "my-" fallback if the + // account lookup is transiently unavailable. + } + } + const agentName = args.name + ? String(args.name) + : defaultByoHandoffName(owner?.username, adapter); + const payload = buildByoHandoffPayload({ + adapter, + os: args.os, + podId: ctx.podId, + agentName, + }); + if ('error' in payload) { + return { content: { ok: false, error: payload.error }, error: 'handoff_invalid' }; + } + const AgentMessageService = require('./agentMessageService'); + const result = await AgentMessageService.postMessage({ + agentName: ctx.agentName, + instanceId: ctx.instanceId, + podId: ctx.podId, + // The structured payload is the handoff. This fixed content is a + // compatibility value for message storage and is not rendered as + // the instructions by the card. + content: 'Connect a local agent with the command below.', + messageType: 'card', + displayName: ctx.displayName, + installationConfig: ctx.installationConfig, + metadata: { source: 'native-runtime', handoff: 'byo' }, + payload, + noOutboundRelay: Boolean(ctx.webTainted), + answeringMessageId: ctx.answeringMessageId, + }); + return { content: { ok: true, posted: true, result } }; + } + case 'commonly_create_task': { const title = String(args.title || '').trim(); if (!title) { From b2bd07ea6eb5af31c602152a5afefdf0b3c4f031 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Thu, 22 Oct 2026 19:01:51 -0700 Subject: [PATCH 2/6] fix(cli): clear host env from public bwrap seats Agent-Model: codex/gpt-5.6-luna/xhigh Co-authored-by: quire --- .../adapters.claude.environment.test.mjs | 13 +++++++++- cli/__tests__/bwrap.test.mjs | 21 ++++++++++++++++ cli/src/lib/adapters/claude.js | 24 +++++++++++++------ cli/src/lib/adapters/opencode.js | 1 + cli/src/lib/adapters/qwen.js | 7 +++++- cli/src/lib/sandbox/bwrap.js | 9 +++++-- 6 files changed, 64 insertions(+), 11 deletions(-) diff --git a/cli/__tests__/adapters.claude.environment.test.mjs b/cli/__tests__/adapters.claude.environment.test.mjs index 0081dc97db..ae9f103fb1 100644 --- a/cli/__tests__/adapters.claude.environment.test.mjs +++ b/cli/__tests__/adapters.claude.environment.test.mjs @@ -718,7 +718,12 @@ describe('claude adapter — ctx.environment', () => { await claude.spawn('hi', { sessionId: null, cwd, - env: { PATH: process.env.PATH }, + env: { + PATH: process.env.PATH, + COMMONLY_HOST_SECRET: 'must-not-reach-public-seat', + AWS_SECRET_ACCESS_KEY: 'must-not-reach-public-seat', + }, + commitAttributionEnv: { COMMONLY_AGENT_SEAT_NAME: 'Public Linux Seat' }, environment: { sandbox: { trust: 'public' } }, _spawnImpl: impl, _detectBwrap: () => ({ available: true, path: 'bwrap' }), @@ -727,6 +732,12 @@ describe('claude adapter — ctx.environment', () => { expect(calls[0].cmd).toBe('bwrap'); expect(calls[0].args).toContain('--unshare-all'); expect(calls[0].args).toContain('--die-with-parent'); + // Mutation guard: removing --clearenv would let the host process env + // back into the namespace even though the adapter env is filtered. + expect(calls[0].args).toContain('--clearenv'); + expect(calls[0].opts.env.COMMONLY_HOST_SECRET).toBeUndefined(); + expect(calls[0].opts.env.AWS_SECRET_ACCESS_KEY).toBeUndefined(); + expect(calls[0].opts.env.COMMONLY_AGENT_SEAT_NAME).toBe('Public Linux Seat'); // The inner claude argv rides after `--`, inside the namespace. const inner = calls[0].args.slice(calls[0].args.indexOf('--') + 1); expect(inner[0]).toMatch(/claude$/); diff --git a/cli/__tests__/bwrap.test.mjs b/cli/__tests__/bwrap.test.mjs index 3474168b5d..6841726821 100644 --- a/cli/__tests__/bwrap.test.mjs +++ b/cli/__tests__/bwrap.test.mjs @@ -77,6 +77,26 @@ describe('wrapArgvWithBwrap', () => { expect(argv[homeIndex + 1]).toBe('/tmp/commonly-qwen/home'); }); + itLinux('restores only the caller-provided environment after clearenv', () => { + const argv = wrapArgvWithBwrap(['claude'], {}, { + workspacePath: '/tmp/ws', + environment: { + PATH: '/usr/bin', + LANG: 'C.UTF-8', + COMMONLY_AGENT_SEAT_NAME: 'Public Linux Seat', + }, + }); + const separator = argv.indexOf('--'); + const sandboxArgs = argv.slice(0, separator); + expect(sandboxArgs).toEqual(expect.arrayContaining([ + '--clearenv', + '--setenv', 'PATH', '/usr/bin', + '--setenv', 'LANG', 'C.UTF-8', + '--setenv', 'COMMONLY_AGENT_SEAT_NAME', 'Public Linux Seat', + '--setenv', 'HOME', '/tmp/ws', + ])); + }); + itMac('throws the documented macOS message on darwin', () => { expect(() => wrapArgvWithBwrap(['claude', '-p', 'hi'], {}, { workspacePath: '/tmp/ws', @@ -91,6 +111,7 @@ describe('wrapArgvWithBwrap', () => { ); expect(argv[0]).toBe('bwrap'); expect(argv).toContain('--unshare-all'); + expect(argv).toContain('--clearenv'); expect(argv).toContain('--share-net'); expect(argv).toContain('--die-with-parent'); expect(argv).toContain('--new-session'); diff --git a/cli/src/lib/adapters/claude.js b/cli/src/lib/adapters/claude.js index 1184fbdb18..cc625d69a2 100644 --- a/cli/src/lib/adapters/claude.js +++ b/cli/src/lib/adapters/claude.js @@ -200,13 +200,15 @@ const preparePublicClaudeState = async (ctx) => { return { statePath, tmpPath, configPath }; }; -const buildClaudeEnv = (input, state, expansionEnv = {}, credentialFile = null) => { +const buildClaudeEnv = (input, state, expansionEnv = {}, credentialFile = null, publicSeat = false) => { const source = input || process.env; - const output = state ? {} : { ...source }; - if (state) { + const output = state || publicSeat ? {} : { ...source }; + if (state || publicSeat) { for (const [key, value] of Object.entries(source)) { if (PUBLIC_SAFE_ENV.test(key)) output[key] = value; } + } + if (state) { output.HOME = state.statePath; output.TMPDIR = state.tmpPath; // Claude keeps its temp root at /private/tmp/claude- unless told @@ -706,6 +708,7 @@ const prepareArgv = async (innerArgv, ctx) => { const claudeBin = resolveClaudePath(claudeEnv); const wrapped = wrapArgvWithBwrap([claudeBin, ...innerArgv], env, { workspacePath: ctx.cwd, + environment: claudeEnv, readOnlyPaths: [ctx.mcpConfigDir, commitHooksPath, originalHooksPath, ...(ctx.mcpReadDirectories || [])].filter(Boolean), }); return { cmd: wrapped[0], args: wrapped.slice(1), env: claudeEnv }; @@ -819,14 +822,16 @@ export default { let mcpConfig = null; let publicClaudeState = null; let publicSkillArgs = []; + const publicTrust = ctx.environment?.sandbox?.trust === 'public'; + const publicNativeSandbox = publicTrust + && PUBLIC_SANDBOX_MODES.has(resolvePublicSandboxMode(ctx.environment?.sandbox)); + const publicBwrapSandbox = publicTrust + && resolvePublicSandboxMode(ctx.environment?.sandbox) === 'bwrap'; try { // Trust is normalized at entry (legacy `internal` reads as public). The // MCP policy follows trust alone, whatever confines the seat: a public // bwrap seat needs a host-prepared build exactly as a Seatbelt one does // (Rhea on #2247). - const publicTrust = ctx.environment?.sandbox?.trust === 'public'; - const publicNativeSandbox = publicTrust - && PUBLIC_SANDBOX_MODES.has(resolvePublicSandboxMode(ctx.environment?.sandbox)); if (publicNativeSandbox) { publicClaudeState = await preparePublicClaudeState(ctx); } @@ -845,8 +850,9 @@ export default { publicClaudeState, mcpConfig?.expansionEnv, mcpConfig?.credential?.path || null, + publicTrust, ); - if (publicNativeSandbox && ctx.commitAttributionEnv) { + if ((publicNativeSandbox || publicBwrapSandbox) && ctx.commitAttributionEnv) { Object.assign(claudeEnv, ctx.commitAttributionEnv); } const spawnCtx = { @@ -902,8 +908,12 @@ export default { publicClaudeState, mcpConfig?.expansionEnv, mcpConfig?.credential?.path || null, + publicTrust, ), }; + if ((publicNativeSandbox || publicBwrapSandbox) && ctx.commitAttributionEnv) { + Object.assign(retryCtx.claudeEnv, ctx.commitAttributionEnv); + } const retry = await prepareArgv(retryBase, retryCtx); const stdout = await runClaude({ cmd: retry.cmd, diff --git a/cli/src/lib/adapters/opencode.js b/cli/src/lib/adapters/opencode.js index 6d8789fe8b..f4956221bc 100644 --- a/cli/src/lib/adapters/opencode.js +++ b/cli/src/lib/adapters/opencode.js @@ -817,6 +817,7 @@ export default { }, }, { workspacePath: ctx.cwd, + environment: childEnv, readOnlyPaths: [ tempDir, binary, diff --git a/cli/src/lib/adapters/qwen.js b/cli/src/lib/adapters/qwen.js index 92bf318dc1..905bb24ccf 100644 --- a/cli/src/lib/adapters/qwen.js +++ b/cli/src/lib/adapters/qwen.js @@ -327,7 +327,12 @@ export default { ], }, }, - }, { workspacePath: ctx.cwd, homePath: home, readOnlyPaths: [tempDir, executable, packageDir, process.execPath, ...mcpReadDirectories].filter(Boolean) }); + }, { + workspacePath: ctx.cwd, + homePath: home, + environment: env, + readOnlyPaths: [tempDir, executable, packageDir, process.execPath, ...mcpReadDirectories].filter(Boolean), + }); } else if (isPublic) { if ((ctx._platform || process.platform) !== 'darwin') { throw new Error(`public Qwen sandbox.mode=${mode} requires Seatbelt on macOS or bwrap on Linux`); diff --git a/cli/src/lib/sandbox/bwrap.js b/cli/src/lib/sandbox/bwrap.js index 393a98a7e9..36ed2e3429 100644 --- a/cli/src/lib/sandbox/bwrap.js +++ b/cli/src/lib/sandbox/bwrap.js @@ -92,8 +92,8 @@ export const detectBwrap = ({ spawn = spawnSync, platform = process.platform } = * user sees the gap before they think they're protected. * * The expansion order matches `bwrap`'s left-to-right argument application: - * isolation flags first, then binds (later binds win on collision), then - * --setenv, then `--` and the inner argv. + * isolation flags first, then binds (later binds win on collision), then the + * explicit child environment and HOME, then `--` and the inner argv. */ export const wrapArgvWithBwrap = (innerArgv, env, opts = {}) => { if (process.platform !== 'linux') { @@ -112,6 +112,7 @@ export const wrapArgvWithBwrap = (innerArgv, env, opts = {}) => { : ['--share-net']; const flags = [ + '--clearenv', '--unshare-all', ...networkFlags, '--die-with-parent', @@ -149,6 +150,10 @@ export const wrapArgvWithBwrap = (innerArgv, env, opts = {}) => { flags.push('--bind', opts.workspacePath, opts.workspacePath); flags.push('--chdir', opts.workspacePath); + for (const [name, value] of Object.entries(opts.environment || {})) { + if (value === undefined || value === null) continue; + flags.push('--setenv', name, String(value)); + } flags.push('--setenv', 'HOME', opts.homePath || opts.workspacePath); return ['bwrap', ...flags, '--', ...innerArgv]; From 94abe80dcfa7614de4b0a9c3ad28b2706268dad9 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Thu, 22 Oct 2026 20:58:45 -0700 Subject: [PATCH 3/6] fix(runtime): remove duplicate local handoff dispatch Co-authored-by: quire Agent-Model: codex/gpt-5.6-luna/xhigh --- backend/services/nativeRuntimeService.ts | 45 ------------------------ 1 file changed, 45 deletions(-) diff --git a/backend/services/nativeRuntimeService.ts b/backend/services/nativeRuntimeService.ts index 95e4c71618..d906ed14f0 100644 --- a/backend/services/nativeRuntimeService.ts +++ b/backend/services/nativeRuntimeService.ts @@ -859,51 +859,6 @@ async function dispatchTool( return { content: { ok: true, posted: true, result } }; } - case 'commonly_offer_local_agent': { - const adapter = String(args.adapter || '') as ByoHandoffAdapter; - let owner: { username?: string } | null = null; - if (ctx.installation?.installedBy) { - try { - owner = await (require('../models/User')).findById(ctx.installation.installedBy) - .select('username').lean() as { username?: string } | null; - } catch { - // A username is only a display-name seed. The handoff remains - // actionable with the stable "my-" fallback if the - // account lookup is transiently unavailable. - } - } - const agentName = args.name - ? String(args.name) - : defaultByoHandoffName(owner?.username, adapter); - const payload = buildByoHandoffPayload({ - adapter, - os: args.os, - podId: ctx.podId, - agentName, - }); - if ('error' in payload) { - return { content: { ok: false, error: payload.error }, error: 'handoff_invalid' }; - } - const AgentMessageService = require('./agentMessageService'); - const result = await AgentMessageService.postMessage({ - agentName: ctx.agentName, - instanceId: ctx.instanceId, - podId: ctx.podId, - // The structured payload is the handoff. This fixed content is a - // compatibility value for message storage and is not rendered as - // the instructions by the card. - content: 'Connect a local agent with the command below.', - messageType: 'card', - displayName: ctx.displayName, - installationConfig: ctx.installationConfig, - metadata: { source: 'native-runtime', handoff: 'byo' }, - payload, - noOutboundRelay: Boolean(ctx.webTainted), - answeringMessageId: ctx.answeringMessageId, - }); - return { content: { ok: true, posted: true, result } }; - } - case 'commonly_create_task': { const title = String(args.title || '').trim(); if (!title) { From b820b0c041c816a68fd6d9466edde513c8becf99 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:10:18 -0700 Subject: [PATCH 4/6] fix(cli): keep bwrap env out of argv Co-authored-by: quire Agent-Model: codex/gpt-5.6-luna/xhigh --- .../adapters.claude.environment.test.mjs | 6 ++--- cli/__tests__/bwrap.test.mjs | 27 ++++++++----------- cli/src/lib/adapters/claude.js | 1 - cli/src/lib/adapters/opencode.js | 1 - cli/src/lib/adapters/qwen.js | 1 - cli/src/lib/sandbox/bwrap.js | 9 +++---- 6 files changed, 17 insertions(+), 28 deletions(-) diff --git a/cli/__tests__/adapters.claude.environment.test.mjs b/cli/__tests__/adapters.claude.environment.test.mjs index ae9f103fb1..d3ccee9300 100644 --- a/cli/__tests__/adapters.claude.environment.test.mjs +++ b/cli/__tests__/adapters.claude.environment.test.mjs @@ -732,9 +732,9 @@ describe('claude adapter — ctx.environment', () => { expect(calls[0].cmd).toBe('bwrap'); expect(calls[0].args).toContain('--unshare-all'); expect(calls[0].args).toContain('--die-with-parent'); - // Mutation guard: removing --clearenv would let the host process env - // back into the namespace even though the adapter env is filtered. - expect(calls[0].args).toContain('--clearenv'); + // The adapter passes its filtered environment to bwrap as the process + // environment; values are not serialized into bwrap argv. + expect(calls[0].args).not.toContain('--clearenv'); expect(calls[0].opts.env.COMMONLY_HOST_SECRET).toBeUndefined(); expect(calls[0].opts.env.AWS_SECRET_ACCESS_KEY).toBeUndefined(); expect(calls[0].opts.env.COMMONLY_AGENT_SEAT_NAME).toBe('Public Linux Seat'); diff --git a/cli/__tests__/bwrap.test.mjs b/cli/__tests__/bwrap.test.mjs index 6841726821..5f4553753f 100644 --- a/cli/__tests__/bwrap.test.mjs +++ b/cli/__tests__/bwrap.test.mjs @@ -77,24 +77,20 @@ describe('wrapArgvWithBwrap', () => { expect(argv[homeIndex + 1]).toBe('/tmp/commonly-qwen/home'); }); - itLinux('restores only the caller-provided environment after clearenv', () => { + itLinux('does not put child environment values in bwrap argv', () => { + const environment = { + PATH: '/tmp/filtered-path-value', + LANG: 'filtered-lang-value', + COMMONLY_AGENT_SEAT_NAME: 'filtered-seat-name-value', + COMMONLY_AGENT_TOKEN: 'secret-that-must-stay-out-of-argv', + }; const argv = wrapArgvWithBwrap(['claude'], {}, { workspacePath: '/tmp/ws', - environment: { - PATH: '/usr/bin', - LANG: 'C.UTF-8', - COMMONLY_AGENT_SEAT_NAME: 'Public Linux Seat', - }, + environment, }); - const separator = argv.indexOf('--'); - const sandboxArgs = argv.slice(0, separator); - expect(sandboxArgs).toEqual(expect.arrayContaining([ - '--clearenv', - '--setenv', 'PATH', '/usr/bin', - '--setenv', 'LANG', 'C.UTF-8', - '--setenv', 'COMMONLY_AGENT_SEAT_NAME', 'Public Linux Seat', - '--setenv', 'HOME', '/tmp/ws', - ])); + expect(argv).not.toContain('--clearenv'); + for (const value of Object.values(environment)) expect(argv).not.toContain(value); + expect(argv).toEqual(expect.arrayContaining(['--setenv', 'HOME', '/tmp/ws'])); }); itMac('throws the documented macOS message on darwin', () => { @@ -111,7 +107,6 @@ describe('wrapArgvWithBwrap', () => { ); expect(argv[0]).toBe('bwrap'); expect(argv).toContain('--unshare-all'); - expect(argv).toContain('--clearenv'); expect(argv).toContain('--share-net'); expect(argv).toContain('--die-with-parent'); expect(argv).toContain('--new-session'); diff --git a/cli/src/lib/adapters/claude.js b/cli/src/lib/adapters/claude.js index cc625d69a2..d0a23ebc00 100644 --- a/cli/src/lib/adapters/claude.js +++ b/cli/src/lib/adapters/claude.js @@ -708,7 +708,6 @@ const prepareArgv = async (innerArgv, ctx) => { const claudeBin = resolveClaudePath(claudeEnv); const wrapped = wrapArgvWithBwrap([claudeBin, ...innerArgv], env, { workspacePath: ctx.cwd, - environment: claudeEnv, readOnlyPaths: [ctx.mcpConfigDir, commitHooksPath, originalHooksPath, ...(ctx.mcpReadDirectories || [])].filter(Boolean), }); return { cmd: wrapped[0], args: wrapped.slice(1), env: claudeEnv }; diff --git a/cli/src/lib/adapters/opencode.js b/cli/src/lib/adapters/opencode.js index f4956221bc..6d8789fe8b 100644 --- a/cli/src/lib/adapters/opencode.js +++ b/cli/src/lib/adapters/opencode.js @@ -817,7 +817,6 @@ export default { }, }, { workspacePath: ctx.cwd, - environment: childEnv, readOnlyPaths: [ tempDir, binary, diff --git a/cli/src/lib/adapters/qwen.js b/cli/src/lib/adapters/qwen.js index 905bb24ccf..be3bc32557 100644 --- a/cli/src/lib/adapters/qwen.js +++ b/cli/src/lib/adapters/qwen.js @@ -330,7 +330,6 @@ export default { }, { workspacePath: ctx.cwd, homePath: home, - environment: env, readOnlyPaths: [tempDir, executable, packageDir, process.execPath, ...mcpReadDirectories].filter(Boolean), }); } else if (isPublic) { diff --git a/cli/src/lib/sandbox/bwrap.js b/cli/src/lib/sandbox/bwrap.js index 36ed2e3429..b83bf157a8 100644 --- a/cli/src/lib/sandbox/bwrap.js +++ b/cli/src/lib/sandbox/bwrap.js @@ -93,7 +93,9 @@ export const detectBwrap = ({ spawn = spawnSync, platform = process.platform } = * * The expansion order matches `bwrap`'s left-to-right argument application: * isolation flags first, then binds (later binds win on collision), then the - * explicit child environment and HOME, then `--` and the inner argv. + * isolated HOME, then `--` and the inner argv. The caller passes the filtered + * child environment as the bwrap process environment; values must not be + * serialized into argv, where they would be visible in the process list. */ export const wrapArgvWithBwrap = (innerArgv, env, opts = {}) => { if (process.platform !== 'linux') { @@ -112,7 +114,6 @@ export const wrapArgvWithBwrap = (innerArgv, env, opts = {}) => { : ['--share-net']; const flags = [ - '--clearenv', '--unshare-all', ...networkFlags, '--die-with-parent', @@ -150,10 +151,6 @@ export const wrapArgvWithBwrap = (innerArgv, env, opts = {}) => { flags.push('--bind', opts.workspacePath, opts.workspacePath); flags.push('--chdir', opts.workspacePath); - for (const [name, value] of Object.entries(opts.environment || {})) { - if (value === undefined || value === null) continue; - flags.push('--setenv', name, String(value)); - } flags.push('--setenv', 'HOME', opts.homePath || opts.workspacePath); return ['bwrap', ...flags, '--', ...innerArgv]; From 8b4add72e517f3ef1b377d80afed04ad5796cea1 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:19:04 -0700 Subject: [PATCH 5/6] test(cli): assert bwrap env at spawn boundary Co-authored-by: quire Agent-Model: codex/gpt-5.6-luna/xhigh --- .../adapters.claude.environment.test.mjs | 8 +++++--- cli/__tests__/bwrap.test.mjs | 16 ---------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/cli/__tests__/adapters.claude.environment.test.mjs b/cli/__tests__/adapters.claude.environment.test.mjs index d3ccee9300..c4c6c04c1d 100644 --- a/cli/__tests__/adapters.claude.environment.test.mjs +++ b/cli/__tests__/adapters.claude.environment.test.mjs @@ -735,9 +735,11 @@ describe('claude adapter — ctx.environment', () => { // The adapter passes its filtered environment to bwrap as the process // environment; values are not serialized into bwrap argv. expect(calls[0].args).not.toContain('--clearenv'); - expect(calls[0].opts.env.COMMONLY_HOST_SECRET).toBeUndefined(); - expect(calls[0].opts.env.AWS_SECRET_ACCESS_KEY).toBeUndefined(); - expect(calls[0].opts.env.COMMONLY_AGENT_SEAT_NAME).toBe('Public Linux Seat'); + expect(calls[0].args).not.toContain('must-not-reach-public-seat'); + expect(calls[0].opts.env).toEqual({ + PATH: process.env.PATH, + COMMONLY_AGENT_SEAT_NAME: 'Public Linux Seat', + }); // The inner claude argv rides after `--`, inside the namespace. const inner = calls[0].args.slice(calls[0].args.indexOf('--') + 1); expect(inner[0]).toMatch(/claude$/); diff --git a/cli/__tests__/bwrap.test.mjs b/cli/__tests__/bwrap.test.mjs index 5f4553753f..3474168b5d 100644 --- a/cli/__tests__/bwrap.test.mjs +++ b/cli/__tests__/bwrap.test.mjs @@ -77,22 +77,6 @@ describe('wrapArgvWithBwrap', () => { expect(argv[homeIndex + 1]).toBe('/tmp/commonly-qwen/home'); }); - itLinux('does not put child environment values in bwrap argv', () => { - const environment = { - PATH: '/tmp/filtered-path-value', - LANG: 'filtered-lang-value', - COMMONLY_AGENT_SEAT_NAME: 'filtered-seat-name-value', - COMMONLY_AGENT_TOKEN: 'secret-that-must-stay-out-of-argv', - }; - const argv = wrapArgvWithBwrap(['claude'], {}, { - workspacePath: '/tmp/ws', - environment, - }); - expect(argv).not.toContain('--clearenv'); - for (const value of Object.values(environment)) expect(argv).not.toContain(value); - expect(argv).toEqual(expect.arrayContaining(['--setenv', 'HOME', '/tmp/ws'])); - }); - itMac('throws the documented macOS message on darwin', () => { expect(() => wrapArgvWithBwrap(['claude', '-p', 'hi'], {}, { workspacePath: '/tmp/ws', From 9e6bb484e70977a2eeb46431debf1131e2357936 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 23 Oct 2026 13:15:34 -0700 Subject: [PATCH 6/6] Bump CLI version for handoff scope Co-authored-by: quire Agent-Model: codex/gpt-5.6-luna/xhigh --- cli/package-lock.json | 4 ++-- cli/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index f7f87ac264..e960a399c1 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "@commonlyai/cli", - "version": "0.1.133", + "version": "0.1.137", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@commonlyai/cli", - "version": "0.1.133", + "version": "0.1.137", "license": "Apache-2.0", "dependencies": { "commander": "^12.0.0" diff --git a/cli/package.json b/cli/package.json index 1c4ad89f7b..b6a01f96fc 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@commonlyai/cli", - "version": "0.1.133", + "version": "0.1.137", "license": "Apache-2.0", "description": "The Commonly CLI — connect agents, manage pods, iterate fast", "type": "module",