Ten findings from a review of the first commit. The ones that bite in normal use:
An unchecked mktemp left EPHEMERAL empty, which becomes `--user-data-dir=` — and Chrome reads that as
"use the default profile", i.e. the browser the user actually signs into. `set -u` does not catch it,
because the variable is set, just empty. Now checked and refused. That branch also never exec'd and
had no way to reach the browser with a signal; it now backgrounds, records the pid, and traps.
Signal handlers registered AFTER a build that takes minutes, so a pm2 restart during a first start
killed this process and left cargo compiling as an orphan — then started a second one on the same
target directory. Handlers now register first and shutdown kills the build.
`which('iris')` treated presence as identity. A truncated binary from an interrupted install is
present, reports ready, and never rebuilds. It now runs `--version` and checks — the same lesson
os-deps.ts already learned as `identify`.
Cookies were copied without Cookies-wal. These are WAL databases, so the newest session may exist
only in the log — copy the .db alone and the capture is signed out for exactly the account that just
signed in. PLUGIN.md had this backwards, citing WAL as the reason it was safe; corrected there too.
Also: --password-store=basic was missing from the branch that actually runs, working only because
chromiumoxide's DEFAULT_ARGS happens to supply it; seeding failures were silent, so the skill's
"sign in again" advice was offered for an unreadable profile it cannot fix; only --user-data-dir=X was
detected and not the space-separated form; installWrapper was unguarded at top level and crash-looped
on an unwritable ~/.local/bin; and it truncated the wrapper in place rather than renaming over it.
The searchPath copy is gone — the platform now exports locateBinary and this asks it. That copy is
what made the manifest probe refuse a rustup-only machine the sidecar could build on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
69 lines
4.7 KiB
Markdown
69 lines
4.7 KiB
Markdown
# Iris — the decision record
|
|
|
|
Written 2026-08-20, the day the plugin was designed in conversation and first laid down. The decisions
|
|
below are the conversation's residue; where one reverses later, record it here rather than deleting it.
|
|
|
|
## Not a fork
|
|
|
|
Upstream (`iris-screenshot` on crates.io, github.com/brijr/iris) is used as-is. The obvious reason to
|
|
fork was authentication — iris has no `--cookie`/`--header` flags — and the decision was that flags are
|
|
the wrong mechanism anyway: credentials on command lines end up in transcripts and process lists. The
|
|
session lives in a browser profile the *user* signs into through the desktop plugin, and the capture
|
|
borrows a copy. The human authenticates; the agent only ever captures.
|
|
|
|
## Build with cargo, in the sidecar
|
|
|
|
The owner's call: cargo is a declared dependency, installed before iris, and iris compiles from source
|
|
on the machine. No prebuilt releases to host, no artefacts to cross-compile, and the plugin has no
|
|
binary of its own to distribute.
|
|
|
|
The platform's installer deliberately has no build step (`osDependencies` installs packages and fetches
|
|
release binaries, nothing more), so the build lives in the one process the platform starts for a plugin:
|
|
the sidecar. First start compiles; later starts probe and skip. `--locked` honours upstream's lockfile —
|
|
the platform's frozen-lockfile principle, one language over.
|
|
|
|
Known consequence, accepted: **distro cargo is usually too old.** Upstream declares `rust-version =
|
|
1.88`; Ubuntu's apt cargo is ~1.75. The manifest still declares distro packages (right on Arch/Fedora,
|
|
and the honest default), and the sidecar checks the version *before* building and prints the rustup
|
|
one-liner when it refuses. Auto-running rustup's curl-pipe-sh was considered and rejected — that is a
|
|
decision the owner makes at a terminal, not one a sidecar makes silently.
|
|
|
|
## No browser in `osDependencies`
|
|
|
|
Three reasons. Ubuntu's apt `chromium` is a snap stub, so a truthful package map does not exist across
|
|
distros. The user prefers Brave, which is Chromium underneath — same CDP, same profile format — so the
|
|
right declaration is "any of a family", which `osDependencies` cannot express. And the browser this
|
|
plugin *wants* is the desktop plugin's, because a browser nobody can see is a browser nobody can sign
|
|
in to. `bin/iris-browser` resolves brave → chromium → chrome at run time and fails with a sentence when
|
|
none exists.
|
|
|
|
## The profile contract
|
|
|
|
`~/.config/officer/browser-profile` is the persistent, per-user profile. The desktop plugin's browser
|
|
should launch with `--user-data-dir` pointing there and `--password-store=basic` (portable cookie
|
|
encryption — keyring-backed cookies do not survive copying). Captures never open that profile directly:
|
|
the wrapper copies `Local State`, `Default/Cookies` and `Default/Login Data` into a throwaway profile,
|
|
which avoids Chrome's profile lock entirely and makes the persistent profile read-only to captures.
|
|
|
|
This contract predates the desktop plugin. When that plugin lands, this path is what it must honour.
|
|
|
|
## Open items, in order of bite
|
|
|
|
1. **Nothing has run.** No live install, no capture, no build. The whole verification table is owed.
|
|
2. **How chromiumoxide launches** decides which wrapper branch runs. If it passes its own
|
|
`--user-data-dir`, the wrapper seeds cookies into that directory; if not, the wrapper appends its
|
|
own ephemeral one. Both are written; neither is proven. Also unproven: that a seeded cookie copy is
|
|
accepted (Chrome ties cookie encryption to `Local State`, which is copied for exactly that reason).
|
|
3. **Members.** The wrapper isolates per-user by `$HOME`, but the binary lands in the *owner's*
|
|
`~/.local/bin`, and a confined member's agent cannot read it there. If members should capture,
|
|
the binary needs a shared home. Deferred until the desktop plugin defines the member story.
|
|
4. **Cookie copy under a live browser.** Copying a SQLite file mid-write can catch a torn state. Being
|
|
WAL does not make this safe — it is what makes the copy INCOMPLETE, because a recent write may live
|
|
only in `Cookies-wal` until the browser checkpoints it. This originally read "the cookie DB is WAL and
|
|
short-lived reads are fine", which had the reasoning backwards: the wrapper copied `Cookies` alone, so
|
|
a session created moments earlier was the one guaranteed to be missing. `-wal` is now copied alongside
|
|
each database (`-shm` deliberately is not — it is live shared memory, rebuilt on open). A torn read of
|
|
the pair is still possible under a live browser and is still unverified.
|
|
5. **`permissions: []`** is honest today (nothing platform-gated to protect) but means the owner cannot
|
|
dock-gate or role-gate the plugin's existence. Revisit if an api/ or web/ arrives.
|