Skip to content

IRIS CLI v1.2.2 — Fix iris update not replacing binary - #4

Merged
mayoalexander merged 2 commits into
mainfrom
dev
Apr 19, 2026
Merged

IRIS CLI v1.2.2 — Fix iris update not replacing binary#4
mayoalexander merged 2 commits into
mainfrom
dev

Conversation

@mayoalexander

Copy link
Copy Markdown

Summary

  • Fix iris update silently failing to replace the binary on macOS (#59972)
  • rm + mv instead of cp -f over running executable
  • Add chmod +x, symlink resolution, post-update version verification
  • Warn user with fallback command if verification fails

Test plan

  • Build passes typecheck
  • Upgrade logic writes script to temp file, avoids Bun template interpolation issues
  • Post-update runs iris --version to verify

🤖 Generated with Claude Code

mayoalexander and others added 2 commits April 19, 2026 10:30
…erification (#59972)

Root cause: `cp -f` over a running macOS binary can silently fail. Also no
chmod +x after extract, no symlink resolution, no post-update verification.

Fixes:
- rm old binary first, then mv new one in (avoids locked-file issue)
- chmod +x after extracting from zip/tar
- Resolve symlinks via realpathSync before determining binDir
- Write upgrade script to temp file (avoids Bun $ template interpolation bugs)
- Verify installed version after update, warn + show fallback if mismatch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 19, 2026 15:31
@mayoalexander
mayoalexander merged commit 4bde813 into main Apr 19, 2026
0 of 6 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes IRIS CLI self-upgrade on macOS where updating could silently fail to replace the running executable, and adds post-upgrade verification.

Changes:

  • Update IRIS upgrade flow to resolve symlinks and replace the binary via rm + mv instead of overwriting in-place.
  • Execute upgrade steps via a generated bash script and add executable bit + runtime verification.
  • Add CLI-side version verification/warning after upgrade and bump package version to 1.2.2.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
packages/opencode/src/installation/index.ts Adjusts IRIS self-upgrade to resolve real binary path, run upgrade via script, and replace binary using rm/mv with a --version check.
packages/opencode/src/cli/cmd/upgrade.ts Adds post-upgrade verification and user guidance if version mismatch is detected.
packages/opencode/package.json Bumps package version to 1.2.2.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +161 to +162
# Verify the new binary works
"${binDir}/iris" --version

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.
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.
Comment on lines +73 to +80
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}`)
}

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.
Comment on lines +157 to +162
# 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

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.
mayoalexander added a commit that referenced this pull request Aug 19, 2026
…ed on a 404

`iris integrations list` collapsed every probe failure into "unverified — could
not probe", which tells the reader nothing and cost real debugging hours.

Two worse bugs surfaced while fixing that:

1. The gmail and google-drive probes returned "verified" for ANY response that
   was not 401/403 — including 404 and 500. Both probe endpoints currently 404
   (/api/v1/leads/0/gmail-threads, /api/v1/integrations/exec), so Gmail has been
   rendering a green [verified] while its probe does not resolve at all. The
   health signal on those rows was meaningless.

2. Probe results were keyed by integration TYPE, not id, and the drive probe
   never passed integration_id. All three google-drive accounts (#4, #11, #12)
   therefore shared one result taken from whichever connection the API picks by
   default — two accounts could be dead and every row would show the third's
   status. The comment directly above that code says multi-account visibility is
   the point of the command.

Changes:

- Probes return { state, reason, fix } instead of a bare string, so every row
  states its cause and, where one exists, the command that fixes it.
- New "unknown" state, distinct from failure. A 404 on the PROBE path means we
  cannot determine the integration's health; claiming either verified or
  unverified would be unsupported. Rendered neutral rather than red, because the
  fix belongs to us, not the user.
- Network failures are classified — connection refused / timeout / DNS — rather
  than collapsing into one string.
- Results keyed by integration id; the drive probe passes integration_id so each
  account is genuinely probed.
- The calendar probe distinguishes bridge-not-running, no-bridge-key,
  key-rejected and HTTP-n, each with `iris hive doctor` as the fix.

    before  gmail #3            [verified]
    after   gmail #3            [unknown] — probe endpoint returned 404; status not determined
    before  google-calendar #2  [unverified] — could not probe
    after   google-calendar #2  [unverified] — no bridge key configured → iris hive doctor

Verified by running the command from source, not by reading the diff. Typecheck
clean; the remaining tsc error (session/llm.ts:88 TS2589) is pre-existing and
reproduces with this change stashed.

Refs #180929, #181016

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mayoalexander added a commit that referenced this pull request Aug 24, 2026
Ran `iris leads pulse 16750` on a co-founder and the output disagreed with
itself in three places and pointed at commands that do not exist in a fourth.

1. Duplicate detection listed leads it had already rejected. The email/phone
   identity check built `duplicateLeadIds`, but the printed list and `bestDup`
   were derived from `allMatches` — the raw fuzzy-name search. The header said
   "(1)" above two rows, and the second row was #4 "Unknown", a record with no
   email and no phone. `bestDup` feeds the interactive merge prompt, and
   `iris leads merge` DELETES the losing record, so this could offer to delete
   a lead that was never a duplicate.

2. Four of twelve Fix It hints named commands that do not exist:
     iris leads edit          -> iris leads update
     iris leads upload (x2)   -> iris proposals create / iris deliver
     iris leads kb <id> add   -> iris leads kb <id> --generate
   There is no `leads upload`; deliverables are posted by `iris deliver`.

3. Calendar and Apple Mail collapsed the bridge response to `HTTP 503` and
   hinted "check bridge: iris hive doctor". The bridge was fine — it is listed
   as verified two lines below. The 503 body said exactly what was wrong ("No
   permission to read the Calendar store"), and we threw it away, turning a
   Full Disk Access problem into a bridge investigation.

4. iMessage reported "connected + verified" directly above a scan that failed
   with "No permission to read Messages". Health probed the local Messages DB
   via sqlite3; pulse reads iMessage through the bridge. The terminal holds
   Full Disk Access and iris-daemon does not, so the check proved a fact about
   the wrong process.

Same class as #178282 (Gmail): a health check that verifies something other
than the thing it reports on. Bridge-backed channels now go through one
`bridgeChannelHealth()` helper that probes the endpoint the feature uses and
surfaces the body, and names TCC as TCC.

Verified by running the patched CLI against lead 16750: duplicates now show
one row matching the count, hints resolve to real commands, and the three
bridge channels report the permission failure instead of a phantom 503.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wYnnLvL4QbNbzFsXdZt7n
mayoalexander added a commit that referenced this pull request Aug 30, 2026
…ust that it failed (#182862)

The ticket says --account "fails to resolve existing connections". It does not. Reproduced on
the reporting account:

  alex@freelabel.net  is a GOOGLE-DRIVE account (#4)
  the two GMAIL connections are #3 (no account_email at all) and #14 (admin@vanguardhcs.com)

So there is genuinely no gmail connection at that address, and the resolution was correct. The
defect is the sentence it produced:

  No connection for type='gmail' matching --account='alex@freelabel.net'. Run: iris integrations list

The user runs that, sees Gmail plainly connected, and concludes the flag is broken. The message
described the outcome instead of the evidence, and sent them somewhere that contradicted it.

It now lists the connections of that type, with their ids and addresses, and says which of them
can NEVER match because no account_email is stored — 21 of 25 connections on this account carry
none, so an email match against them cannot succeed and no amount of retrying will help. The fix
is --integration-id or a reconnect, and neither was guessable before.

"Nothing matched" and "nothing is connected" are now different sentences with different fixes.

ALSO, one layer down: `if (!res.ok) return null` meant a 500 on OUR lookup endpoint rendered as
"no connection" — the same conflation as #182861, where a failure in our path became a claim
about the user's integration. That now says the lookup failed and that it implies nothing about
whether the integration is connected.

describeAccountMiss is extracted and exported because the bug was in the sentence, so the
sentence is the thing worth pinning. 5 tests, no transport needed. Typechecked: zero diagnostics
in platform-run.ts (TS2589 in session/llm.ts is pre-existing).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDsGZrjZEh5f2GTtZj9oBK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants