Skip to content

Interceptor skill: four defects in the shipped tools, all reproducible on a current install #1802

Description

@catchingknives

Interceptor skill: four defects in the shipped tools, all reproducible on a current install

Four separate defects in LifeOS/install/skills/Interceptor/Tools/, verified against main as of
commit 36c6f01. Two of them stop a tool from working at all on a current install, one makes a safety
check fail open, and one is a hard refusal on the recommended install route. They share two causes:
the shipped code predates Interceptor 0.23, and it assumes a from-source install of the CLI.

I hit all four in normal use and fixed them locally. Happy to open PRs if the approach below looks
right to you.


1. CleanupTabs.sh never runs: it reads preferences.env from the wrong directory

CleanupTabs.sh resolves the preferences file relative to the skill directory:

PREFS="$SKILL_DIR/preferences.env"

Every other tool in the same directory reads it from USER customizations, including the gate:

# PreflightIsolation.sh, Capture.sh, EnsureTestProfile.sh, LaunchTestProfile.sh
USER_PREFS="${HOME}/.claude/LIFEOS/USER/CUSTOMIZATIONS/SKILLS/Interceptor/preferences.env"

Since the skill ships preferences.env.example rather than preferences.env, the file is never
found, INTERCEPTOR_TEST_CONTEXT_ID is never set, and the script exits 8 on every invocation:

CleanupTabs.sh: INTERCEPTOR_TEST_CONTEXT_ID unset in preferences.env — refusing (no default-to-Default).

Post-run tab hygiene has therefore never run for anyone whose preferences live where the gate expects
them. The message is misleading too: it names a file that exists and is correctly populated.

Fix: read the USER customizations path, and keep the skill-local path as a second candidate for
installs that put it beside the skill.

2. The working-profile deny-list is parsed two different ways, and one of them fails open

preferences.env.example documents the deny-list as comma-separated, and PreflightIsolation.sh
parses it that way:

IFS=',' read -ra _deny_ids <<< "$WORKING_PROFILE_IDS"

CleanupTabs.sh splits the same variable on whitespace:

for deny in ${INTERCEPTOR_WORKING_PROFILE_IDS:-}; do

So the documented value arrives as a single token, never matches, and the refusal never fires:

INTERCEPTOR_WORKING_PROFILE_IDS="my-work-profile,other" bash Tools/CleanupTabs.sh --dry-run
# exit 0, and it proceeds to close tabs in my-work-profile
INTERCEPTOR_WORKING_PROFILE_IDS="my-work-profile,other" bash Tools/PreflightIsolation.sh
# exit 7, correctly refused

This is the only guard on that path, since CleanupTabs.sh does not call the preflight. Note the
mirror-image hole in the gate: a space-separated value arrives there as one token and fails open the
same way.

Fix: split on commas and whitespace in both. For a deny-list, over-splitting is the safe direction.

3. PreflightIsolation.sh hard-fails every signed-pkg install of Interceptor

The extension check knows exactly one location, the from-source pin:

EXT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/Extension"
if [ ! -f "$EXT_DIR/manifest.json" ]; then ... exit 9

The signed installer, which is the recommended install route, puts the extension at
/Library/Application Support/Interceptor/extension and never creates a source checkout. On such a
machine the gate exits 9 and prints remediation that cannot be performed: it points at Tools/Pin.sh,
which needs $INTERCEPTOR_SRC/extension/dist to copy from.

Every browser workflow starts with this gate, so the whole skill is unusable on a signed install until
the operator reads the script and works out why.

Fix: resolve the extension from an ordered candidate list ($INTERCEPTOR_EXT_DIR, the pin, the
signed-install path) and hard-fail only when none exists. Worth adding while you are there: a signed
install version-locks the extension to the CLI, so comparing manifest.json against
interceptor --version catches a partial upgrade, which the from-source path cannot check at all. If
you check every source found rather than the first, a stale leftover pin sitting next to a current
signed install is caught too. That state currently passes, because the pin wins the search and then
takes the warn-and-continue branch when no dist reference is present.

4. VerifyViewport.ts cannot attach on Interceptor 0.23.x, and its shot verb truncates

Two independent problems in one file.

4a. The CDP context id is derived rather than resolved.

const CDP_CONTEXT = `cdp:${(process.env.INTERCEPTOR_VERIFY_CDP_HOST ?? '127.0.0.1').replace(/\./g, '-')}`

Interceptor 0.22 derived the context id from the connect host, giving cdp:127-0-0-1. 0.23 names a
manual-port context after the app instead, giving cdp:app-<port>. Every attach now fails:

[VerifyViewport] attach failed: error: cdp context 'cdp:127-0-0-1' not found

The alias-pruning loop makes it worse: it detaches every context on the port that is not the derived
name, which on 0.23 is the one real context, so it tears down the connection it is about to attach to.

Fix: resolve the id from interceptor macos cdp status, filtered to the instance's own port,
instead of deriving it. Prune after a successful attach rather than before.

4b. sh() reads the CLI through a pipe, which truncates large responses.

const p = Bun.spawn([cmd, ...args], { stdout: 'pipe', stderr: 'pipe' })

The Interceptor CLI truncates any response larger than the pipe buffer when stdout is a pipe, silently
and with exit 0. I filed that upstream as Hacker-Valley-Media/Interceptor#183 with a byte-exact
reproduction. It bites here because shot captures a screenshot over CDP: a 1440x900 page returns
about 1.18 MB and arrives as 65,536 bytes, so the verb dies on

[VerifyViewport] JSON Parse error: Unterminated string

Fix: until the CLI is fixed, spawn with stdout pointed at a temp file and read the file back. That
covers every large payload, not just screenshots.


Environment

LifeOS 7.28.3
interceptor 0.23.12, signed pkg install
OS macOS 26.6.1, arm64
Chrome 151

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions