From 3cb86e1001600774b193647f30458a24439bb777 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Mon, 26 Oct 2026 00:21:53 -0700 Subject: [PATCH 1/3] fix(cli): agent init run hint sets the bare token, and the SDK 401 names COMMONLY_TOKEN (TASK-301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The printed run hint was `COMMONLY_TOKEN=$(cat .commonly-env) python3 …`, but .commonly-env is already KEY=VALUE, so the variable got the literal `COMMONLY_TOKEN=cm_agent_…` and every poll 401'd. The SDK then exited saying the token was "likely revoked", which sent people to re-issue a good token. - Hint is now `env $(cat .commonly-env) python3 .py`, which exports the KEY=VALUE line as written. - init prints the @mention handle (instanceId, else registry agentName), the same rule `attach` uses. - SDK exit message says to check COMMONLY_TOKEN and no longer calls the token revoked. Tests run the printed hint through /bin/sh with a stub python3 and assert the bot receives the bare token. The old hint fails that check. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: sprint-impl Agent-Model: claude/claude-haiku-5-5/default --- cli/__tests__/agent-init-run-hint.test.mjs | 105 +++++++++++++++++++++ cli/src/commands/agent.js | 10 +- examples/sdk/python/commonly.py | 3 +- 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 cli/__tests__/agent-init-run-hint.test.mjs diff --git a/cli/__tests__/agent-init-run-hint.test.mjs b/cli/__tests__/agent-init-run-hint.test.mjs new file mode 100644 index 0000000000..295fb530c6 --- /dev/null +++ b/cli/__tests__/agent-init-run-hint.test.mjs @@ -0,0 +1,105 @@ +/** + * agent-init-run-hint.test.mjs — the run hint `agent init` prints must work + * when pasted, and the SDK's 401 exit must point at COMMONLY_TOKEN. + * + * The hint is executed through /bin/sh with a stub `python3` on PATH that + * prints the COMMONLY_TOKEN it was started with. If the hint doubles the + * KEY= prefix, the stub prints `COMMONLY_TOKEN=cm_agent_…` and this fails. + */ + +import { jest } from '@jest/globals'; +import { execFileSync } from 'child_process'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +import { performInit } from '../src/commands/agent.js'; + +const REPO_ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..'); +const SDK_FILE = path.join(REPO_ROOT, 'examples', 'sdk', 'python', 'commonly.py'); + +const TOKEN = 'cm_agent_walk_echo_test'; + +const makeClient = () => { + const post = jest.fn(async (route, body) => { + if (route === '/api/registry/install') { + return { + installation: { agentName: body.agentName, instanceId: 'default', podId: body.podId }, + runtimeToken: TOKEN, + }; + } + throw new Error(`unexpected POST ${route}`); + }); + return { post, get: jest.fn() }; +}; + +let tmp; +let binDir; + +beforeEach(() => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-init-hint-')); + binDir = path.join(tmp, 'bin'); + fs.mkdirSync(binDir); + // Stub interpreter: prints the token the process was started with, no newline. + fs.writeFileSync(path.join(binDir, 'python3'), '#!/bin/sh\nprintf "%s" "$COMMONLY_TOKEN"\n', { mode: 0o755 }); +}); +afterEach(() => { + fs.rmSync(tmp, { recursive: true, force: true }); +}); + +describe('agent init run hint', () => { + test('the printed command starts the bot with the bare token in COMMONLY_TOKEN', async () => { + const targetDir = path.join(tmp, 'agent'); + const result = await performInit({ + client: makeClient(), + language: 'python', + agentName: 'walk-echo-1026', + podId: 'pod-1', + targetDir, + }); + + const seen = execFileSync('/bin/sh', ['-c', result.runHint], { + cwd: targetDir, + env: { PATH: `${binDir}${path.delimiter}${process.env.PATH}` }, + encoding: 'utf8', + }); + + expect(seen).toBe(TOKEN); + expect(seen).not.toContain('COMMONLY_TOKEN='); + }); + + test('the hint invokes the bot file that init wrote', async () => { + const targetDir = path.join(tmp, 'agent'); + const result = await performInit({ + client: makeClient(), + language: 'python', + agentName: 'walk-echo-1026', + podId: 'pod-1', + targetDir, + }); + + expect(result.runHint).toBe('env $(cat .commonly-env) python3 walk-echo-1026.py'); + expect(fs.existsSync(path.join(targetDir, 'walk-echo-1026.py'))).toBe(true); + }); + + test('the mention handle falls back to the registry agentName when there is no instanceId', async () => { + const result = await performInit({ + client: makeClient(), + language: 'python', + agentName: 'walk-echo-1026', + podId: 'pod-1', + targetDir: path.join(tmp, 'agent'), + }); + + expect(result.mentionHandle).toBe('walk-echo-1026'); + }); +}); + +describe('SDK 401 exit message', () => { + test('tells the user to check COMMONLY_TOKEN and does not call the token revoked', () => { + const sdk = fs.readFileSync(SDK_FILE, 'utf8'); + + expect(sdk).toContain('Check COMMONLY_TOKEN'); + expect(sdk).not.toContain('likely revoked'); + }); +}); diff --git a/cli/src/commands/agent.js b/cli/src/commands/agent.js index 2425cff48d..cd61c2e555 100644 --- a/cli/src/commands/agent.js +++ b/cli/src/commands/agent.js @@ -3354,7 +3354,10 @@ const SDK_SOURCES = { botSrc: () => sdkAsset('python/bot.py', join('hello-world-python', 'bot.py')), sdkOut: 'commonly.py', botOut: (name) => `${name}.py`, - runHint: (name) => `COMMONLY_TOKEN=$(cat .commonly-env) python3 ${name}.py`, + // .commonly-env is already KEY=VALUE, so `env $(cat …)` exports the key as + // written. `COMMONLY_TOKEN=$(cat …)` would set the variable to the literal + // `COMMONLY_TOKEN=cm_agent_…` (a doubled prefix) and the bot would 401. + runHint: (name) => `env $(cat .commonly-env) python3 ${name}.py`, }, }; @@ -3409,6 +3412,9 @@ export const performInit = async ({ }); const installation = installResult.installation || installResult; const instanceId = installation.instanceId || 'default'; + // Same rule as `attach`: the mention handle is the instanceId when there is + // one, otherwise the registry agentName. + const mentionHandle = instanceId !== 'default' ? instanceId : (installation.agentName || agentName); let runtimeToken = installResult.runtimeToken; if (!runtimeToken) { @@ -3434,6 +3440,7 @@ export const performInit = async ({ return { installation, instanceId, + mentionHandle, runtimeToken, files: { sdk: sdkOutPath, @@ -4171,6 +4178,7 @@ Docs: console.log('\nNext:'); console.log(` 1. Edit ${opts.name}.py to handle events.`); console.log(` 2. Run: ${result.runHint}`); + console.log(` 3. In pod ${opts.pod}, @mention ${result.mentionHandle} to talk to it.`); } catch (err) { console.error(`Init failed: ${err.message}`); process.exit(1); diff --git a/examples/sdk/python/commonly.py b/examples/sdk/python/commonly.py index a2442ee1be..c2b4a10f11 100644 --- a/examples/sdk/python/commonly.py +++ b/examples/sdk/python/commonly.py @@ -200,7 +200,8 @@ def run(self, on_event: Callable[[dict], Optional[str]], *, if consecutive_auth_errors >= max_auth_errors: print( f"[commonly] runtime token rejected {consecutive_auth_errors}x in a row — exiting. " - "The token is likely revoked; re-issue or uninstall the agent.", + "Check COMMONLY_TOKEN: it must hold the bare cm_agent_... value, not the " + "KEY=VALUE line from .commonly-env. If it is right, re-issue the token or uninstall the agent.", flush=True, ) return From d3bf4cfaf46912c65bef6d795b9accf74dd2e67b Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Tue, 27 Oct 2026 23:08:26 -0700 Subject: [PATCH 2/3] test(cli): run-hint test uses fileURLToPath, skips the shell run on Windows (TASK-301) - The test path came from new URL(import.meta.url).pathname, which keeps %20 and breaks in any checkout under a path with a space. fileURLToPath fixes it. - The run-hint test executes the hint through /bin/sh, which Windows lacks. It is skipped on win32; the CLI CI runs on POSIX. - Adds a case for the mention handle when the install has a real instanceId. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: kai Agent-Model: claude/claude-haiku-5-5/default Co-authored-by: sprint-impl --- cli/__tests__/agent-init-run-hint.test.mjs | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/cli/__tests__/agent-init-run-hint.test.mjs b/cli/__tests__/agent-init-run-hint.test.mjs index 295fb530c6..3efde6d5a3 100644 --- a/cli/__tests__/agent-init-run-hint.test.mjs +++ b/cli/__tests__/agent-init-run-hint.test.mjs @@ -12,19 +12,20 @@ import { execFileSync } from 'child_process'; import fs from 'fs'; import os from 'os'; import path from 'path'; +import { fileURLToPath } from 'url'; import { performInit } from '../src/commands/agent.js'; -const REPO_ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..'); +const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..'); const SDK_FILE = path.join(REPO_ROOT, 'examples', 'sdk', 'python', 'commonly.py'); const TOKEN = 'cm_agent_walk_echo_test'; -const makeClient = () => { +const makeClient = ({ instanceId = 'default' } = {}) => { const post = jest.fn(async (route, body) => { if (route === '/api/registry/install') { return { - installation: { agentName: body.agentName, instanceId: 'default', podId: body.podId }, + installation: { agentName: body.agentName, instanceId, podId: body.podId }, runtimeToken: TOKEN, }; } @@ -48,7 +49,10 @@ afterEach(() => { }); describe('agent init run hint', () => { - test('the printed command starts the bot with the bare token in COMMONLY_TOKEN', async () => { + // The hint runs through /bin/sh, which Windows does not have. + const posixTest = process.platform === 'win32' ? test.skip : test; + + posixTest('the printed command starts the bot with the bare token in COMMONLY_TOKEN', async () => { const targetDir = path.join(tmp, 'agent'); const result = await performInit({ client: makeClient(), @@ -82,6 +86,18 @@ describe('agent init run hint', () => { expect(fs.existsSync(path.join(targetDir, 'walk-echo-1026.py'))).toBe(true); }); + test('the mention handle is the instanceId when the install has one', async () => { + const result = await performInit({ + client: makeClient({ instanceId: 'aria' }), + language: 'python', + agentName: 'walk-echo-1026', + podId: 'pod-1', + targetDir: path.join(tmp, 'agent'), + }); + + expect(result.mentionHandle).toBe('aria'); + }); + test('the mention handle falls back to the registry agentName when there is no instanceId', async () => { const result = await performInit({ client: makeClient(), From 5a38bad50d7ffb745786ee28d9375cf8154a06e7 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:36:47 -0700 Subject: [PATCH 3/3] fix(cli): keep the agent token out of argv in the init run hint (TASK-301) The hint used env $(cat .commonly-env), which put the token on env's command line until exec. Source the file under set -a in a subshell instead. The /bin/sh test still asserts the bare token reaches the bot. Co-Authored-By: Claude Haiku 5.5 Co-authored-by: sprint-impl Agent-Model: claude/claude-haiku-5-5/default --- cli/__tests__/agent-init-run-hint.test.mjs | 2 +- cli/src/commands/agent.js | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cli/__tests__/agent-init-run-hint.test.mjs b/cli/__tests__/agent-init-run-hint.test.mjs index 3efde6d5a3..3cc708e022 100644 --- a/cli/__tests__/agent-init-run-hint.test.mjs +++ b/cli/__tests__/agent-init-run-hint.test.mjs @@ -82,7 +82,7 @@ describe('agent init run hint', () => { targetDir, }); - expect(result.runHint).toBe('env $(cat .commonly-env) python3 walk-echo-1026.py'); + expect(result.runHint).toBe('(set -a; . ./.commonly-env; exec python3 walk-echo-1026.py)'); expect(fs.existsSync(path.join(targetDir, 'walk-echo-1026.py'))).toBe(true); }); diff --git a/cli/src/commands/agent.js b/cli/src/commands/agent.js index cd61c2e555..1576115f35 100644 --- a/cli/src/commands/agent.js +++ b/cli/src/commands/agent.js @@ -3354,10 +3354,11 @@ const SDK_SOURCES = { botSrc: () => sdkAsset('python/bot.py', join('hello-world-python', 'bot.py')), sdkOut: 'commonly.py', botOut: (name) => `${name}.py`, - // .commonly-env is already KEY=VALUE, so `env $(cat …)` exports the key as - // written. `COMMONLY_TOKEN=$(cat …)` would set the variable to the literal - // `COMMONLY_TOKEN=cm_agent_…` (a doubled prefix) and the bot would 401. - runHint: (name) => `env $(cat .commonly-env) python3 ${name}.py`, + // .commonly-env is already KEY=VALUE. Sourcing it under `set -a` exports + // the key as written and keeps the token out of argv (`env $(cat …)` would + // put it on the command line). `COMMONLY_TOKEN=$(cat …)` would set the + // variable to the literal `COMMONLY_TOKEN=cm_agent_…` and the bot would 401. + runHint: (name) => `(set -a; . ./.commonly-env; exec python3 ${name}.py)`, }, };