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
2 changes: 1 addition & 1 deletion packages/opencode/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.2.1",
"version": "1.2.2",
"name": "opencode",
"displayName": "iris-agent-cli",
"type": "module",
Expand Down
12 changes: 11 additions & 1 deletion packages/opencode/src/cli/cmd/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,18 @@ export const UpgradeCommand = {
spinner.stop(Installation.isIris() ? "IRIS CLI updated" : "Upgrade complete")

if (Installation.isIris()) {
// Also update SDK and bridge if present
// Verify the update actually took effect
const { $ } = await import("bun")
const verifyResult = await $`${process.execPath} --version`.nothrow().quiet().text()
const installedVersion = verifyResult.trim()
if (installedVersion && installedVersion !== target) {
prompts.log.warn(`Expected v${target} but binary reports v${installedVersion}`)
prompts.log.info(`Try: curl -fsSL https://heyiris.io/install-iris.sh | bash`)
} else {
prompts.log.success(`Verified: v${installedVersion}`)
}
Comment on lines +73 to +80

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

The verification uses .nothrow().quiet().text() and then treats an empty/failed command the same as success (it falls into the else branch and prints "Verified"). Consider capturing the process result (including exit code) and only reporting success when exitCode is 0 and a version string was produced.

Copilot uses AI. Check for mistakes.

// Also update SDK and bridge if present
const home = process.env.HOME || ""

const sdkDir = `${home}/.iris/sdk`
Expand Down
35 changes: 25 additions & 10 deletions packages/opencode/src/installation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,36 @@ export namespace Installation {
const ext = platform === "linux" ? "tar.gz" : "zip"
const assetName = `iris-${platform}-${arch}.${ext}`
const releaseUrl = `https://github.com/FREELABEL/iris-opencode/releases/download/v${target}/${assetName}`
const binDir = path.dirname(process.execPath)
// Resolve symlinks to get the real binary path
const realExecPath = await import("fs").then(fs => fs.realpathSync(process.execPath))
const binDir = path.dirname(realExecPath)
const tmpDir = path.join(binDir, ".iris-update-tmp")

cmd = $`set -e
mkdir -p ${tmpDir}
cd ${tmpDir}
curl -fsSL -o ${assetName} ${releaseUrl}
// Use a script file to avoid Bun template literal interpolation issues
const script = `#!/bin/bash
set -e
mkdir -p "${tmpDir}"
cd "${tmpDir}"
curl -fsSL -o "${assetName}" "${releaseUrl}"
if [ "${ext}" = "tar.gz" ]; then
tar -xzf ${assetName}
tar -xzf "${assetName}"
else
unzip -o ${assetName}
unzip -o "${assetName}"
fi
cp -f iris ${binDir}/iris
rm -rf ${tmpDir}
echo "Updated to v${target}"`.env({ ...process.env })
chmod +x iris
# Remove old binary first (avoids overwriting a running executable)
rm -f "${binDir}/iris"
mv iris "${binDir}/iris"
rm -rf "${tmpDir}"
# Verify the new binary works
"${binDir}/iris" --version
Comment on lines +161 to +162

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

With set -e, the final "${binDir}/iris" --version check will cause the whole update to fail if the new binary can't execute (Gatekeeper/quarantine, missing deps, etc.). That prevents the later CLI-level warning/fallback flow from running and makes the update appear failed. Consider making this check non-fatal (capture output/exit code and always exit 0) and let the TypeScript verification/warning handle mismatches.

Suggested change
# Verify the new binary works
"${binDir}/iris" --version
# Best-effort verification only; let the TypeScript layer handle warnings/fallbacks.
set +e
"${binDir}/iris" --version >/dev/null 2>&1
verify_status=$?
set -e
exit 0

Copilot uses AI. Check for mistakes.
Comment on lines +157 to +162

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

The update script deletes the existing "${binDir}/iris" before moving the new binary into place. If the mv fails (download/extract issues, permissions, disk full), this leaves the user with no iris binary at all. Consider doing an atomic swap (move new binary to a temp name in binDir, then rename over), and/or moving the old binary to a backup and restoring it on failure.

Suggested change
# Remove old binary first (avoids overwriting a running executable)
rm -f "${binDir}/iris"
mv iris "${binDir}/iris"
rm -rf "${tmpDir}"
# Verify the new binary works
"${binDir}/iris" --version
newBinary="${binDir}/iris.new.$$"
backupBinary="${binDir}/iris.backup.$$"
restore_previous() {
status=$?
if [ -f "${backupBinary}" ] && [ ! -f "${binDir}/iris" ]; then
mv -f "${backupBinary}" "${binDir}/iris" || true
fi
rm -f "${newBinary}"
rm -rf "${tmpDir}"
exit $status
}
trap restore_previous EXIT
# Stage the new binary in the target directory before replacing the current one.
mv -f iris "${newBinary}"
# Move the current binary aside so it can be restored if anything fails.
if [ -f "${binDir}/iris" ]; then
mv -f "${binDir}/iris" "${backupBinary}"
fi
mv -f "${newBinary}" "${binDir}/iris"
# Verify the new binary works before removing the backup.
"${binDir}/iris" --version
rm -f "${backupBinary}"
rm -rf "${tmpDir}"
trap - EXIT

Copilot uses AI. Check for mistakes.
`
const scriptPath = path.join(tmpDir + "-script.sh")
await import("fs").then(fs => {
fs.mkdirSync(path.dirname(scriptPath), { recursive: true })
fs.writeFileSync(scriptPath, script, { mode: 0o755 })
})
cmd = $`bash ${scriptPath} && rm -f ${scriptPath}`.env({ ...process.env })

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

bash ${scriptPath} && rm -f ${scriptPath} only removes the script on success. If the script fails at any step (curl/unzip/mv/version check), the script file will be left behind in the install directory. Prefer ensuring cleanup in a finally/trap (or use ; rm -f after capturing exit code) so the temp script is removed even on failure.

Suggested change
cmd = $`bash ${scriptPath} && rm -f ${scriptPath}`.env({ ...process.env })
cmd =
$`bash -c 'script_path="$1"; trap '"'"'rm -f "$script_path"'"'"' EXIT; bash "$script_path"' -- ${scriptPath}`.env(
{ ...process.env },
)

Copilot uses AI. Check for mistakes.
} else {
switch (method) {
case "curl":
Expand Down
Loading