Skip to content
Open
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
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ fi
# MUST run before `agent setup` (`setup agent` on older CLIs): that step registers the MCP server, and it derives the MCP host and
# registration name from the persisted environment. Switching afterwards would leave the machine's
# agents pointed at production's MCP server while the CLI talked to staging.
if [ -n "$ENV_NAME" ]; then
if [ -n "$ENV_NAME" ] && [ "${INSTA_UPGRADE:-0}" != "1" ]; then
echo
if ! "$INSTALL_DIR/$BIN" env use "$ENV_NAME"; then
# HARD FAIL, deliberately. The environment was requested and could not be applied, so this
Expand Down
2 changes: 1 addition & 1 deletion src/commands/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export async function upgrade(current: string, deps: UpgradeDeps = {}): Promise<
if (channel === 'npm') {
await run({ cmd: 'npm', args: ['install', '-g', `insta@${latest ?? 'latest'}`], env: process.env })
} else {
const shellEnv = { ...process.env, INSTA_INSTALL_DIR: installDir }
const shellEnv = { ...process.env, INSTA_INSTALL_DIR: installDir, INSTA_UPGRADE: '1' }
const sh = { cmd: 'sh', args: ['-c', `curl -fsSL ${INSTALL_SH} | sh`] }
if (!latest) {
await run({ ...sh, env: shellEnv })
Expand Down
57 changes: 57 additions & 0 deletions test/upgrade-environment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { spawnSync } from 'node:child_process'
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { expect, it } from 'vitest'
import { upgrade } from '../src/commands/upgrade.js'

const installer = readFileSync(new URL('../install.sh', import.meta.url), 'utf8')
const environmentStep = installer.slice(installer.indexOf('# ---- environment'), installer.indexOf('# ---- agent setup'))
const repo = fileURLToPath(new URL('..', import.meta.url))

it.skipIf(process.platform === 'win32').each([undefined, 'prod', 'staging'])(
'binary upgrade preserves saved environment and login with INSTA_ENV=%s', async (override) => {
const home = mkdtempSync(join(tmpdir(), 'insta-upgrade-env-'))
const config = join(home, '.insta', 'config.json')
const saved = { INSTA_ENV: process.env.INSTA_ENV, INSTA_UPDATE_CACHE: process.env.INSTA_UPDATE_CACHE }
try {
mkdirSync(join(home, '.insta'))
const original = JSON.stringify({ apiUrl: 'https://api.staging.instacloud.com', accessToken: 'test-access', refreshToken: 'test-refresh', user: { id: 'test-user' } })
writeFileSync(config, original)
writeFileSync(join(home, 'insta'), '#!/bin/sh\nexec "$TEST_NODE" --import tsx "$TEST_CLI" "$@"\n', { mode: 0o755 })
if (override === undefined) delete process.env.INSTA_ENV
else process.env.INSTA_ENV = override
process.env.INSTA_UPDATE_CACHE = join(home, 'update-check.json')
const runEnvironment = (env: NodeJS.ProcessEnv) => {
const result = spawnSync('sh', ['-c', `set -eu\n${environmentStep}`], {
cwd: repo, encoding: 'utf8', timeout: 10_000,
env: { ...env, HOME: home, INSTALL_DIR: home, BIN: 'insta', ENV_NAME: env.INSTA_ENV ?? '',
TEST_NODE: process.execPath, TEST_CLI: join(repo, 'src/index.ts'), INSTA_NO_AUTOUPDATE: '1' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The spawned real CLI (src/index.ts via the home/insta wrapper) runs the full startup path: trackCommand at src/index.ts posts PostHog telemetry for every command and is awaited, and maybeUpdate can respawn a detached __update-check that hits the npm registry. runEnvironment disables auto-update but not telemetry, so the regression suite sends real network requests (and pollutes prod Telemetry with test events), and offline/slow CI can break the 10 s spawnSync timeout. Add INSTA_NO_TELEMETRY: '1' to the spawned env.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At test/upgrade-environment.test.ts, line 30:

<comment>The spawned real CLI (`src/index.ts` via the `home/insta` wrapper) runs the full startup path: `trackCommand` at src/index.ts posts PostHog telemetry for every command and is awaited, and `maybeUpdate` can respawn a detached `__update-check` that hits the npm registry. `runEnvironment` disables auto-update but not telemetry, so the regression suite sends real network requests (and pollutes prod Telemetry with test events), and offline/slow CI can break the 10 s `spawnSync` timeout. Add `INSTA_NO_TELEMETRY: '1'` to the spawned env.</comment>

<file context>
@@ -0,0 +1,57 @@
+        const result = spawnSync('sh', ['-c', `set -eu\n${environmentStep}`], {
+          cwd: repo, encoding: 'utf8', timeout: 10_000,
+          env: { ...env, HOME: home, INSTALL_DIR: home, BIN: 'insta', ENV_NAME: env.INSTA_ENV ?? '',
+            TEST_NODE: process.execPath, TEST_CLI: join(repo, 'src/index.ts'), INSTA_NO_AUTOUPDATE: '1' },
+        })
+        expect(result.status, result.stderr).toBe(0)
</file context>
Suggested change
TEST_NODE: process.execPath, TEST_CLI: join(repo, 'src/index.ts'), INSTA_NO_AUTOUPDATE: '1' },
TEST_NODE: process.execPath, TEST_CLI: join(repo, 'src/index.ts'), INSTA_NO_AUTOUPDATE: '1', INSTA_NO_TELEMETRY: '1' },

})
expect(result.status, result.stderr).toBe(0)
}
let installs = 0
await upgrade('0.0.1', {
channel: 'binary', latest: '0.0.2', installDir: home, report: () => {}, observe: async () => '0.0.2',
run: async (spec) => {
installs++
expect(spec.env.INSTA_ENV).toBe(override)
runEnvironment(spec.env)
expect(readFileSync(config, 'utf8')).toBe(original)
if (installs === 1) throw new Error('exercise unpinned fallback')
},
})
expect(installs).toBe(2)
expect(readFileSync(config, 'utf8')).toBe(original)
runEnvironment({ ...process.env, INSTA_ENV: 'prod', INSTA_UPGRADE: '0' })
expect(JSON.parse(readFileSync(config, 'utf8')).apiUrl).toBe('https://api.instacloud.com')
} finally {
for (const [key, value] of Object.entries(saved)) {
if (value === undefined) delete process.env[key]
else process.env[key] = value
}
rmSync(home, { recursive: true, force: true })
}
},
)
Loading