Skip to content

fix(hooks): bound the SessionEnd teardown budget; docs(interceptor): Linux + Flatpak browser failure class - #1682

Closed
pkumaschow wants to merge 1 commit into
danielmiessler:mainfrom
pkumaschow:fix/sessionend-teardown-budget-and-flatpak-gotchas
Closed

fix(hooks): bound the SessionEnd teardown budget; docs(interceptor): Linux + Flatpak browser failure class#1682
pkumaschow wants to merge 1 commit into
danielmiessler:mainfrom
pkumaschow:fix/sessionend-teardown-budget-and-flatpak-gotchas

Conversation

@pkumaschow

Copy link
Copy Markdown

Two independent changes found while bringing a Linux install up. Happy to split into separate PRs if you'd prefer — they share only the session that surfaced them.

1. fix(hooks) — take DocIntegrity off the SessionEnd teardown budget

The SessionEnd hook was being killed by the harness, surfacing only as Hook cancelled with no detail. A timeout bounded the failure but didn't prevent it.

The cause isn't one slow call, it's a serial chain inside the 30s kill window:

Step Cost
transcript parses ×2 ~0.2s (74ms/pass over 14MB — not the culprit despite looking like one)
deterministic checks small
runInferenceAnalysis ≤15s — spawns the claude CLI; measured 6573ms on a 10318-char context
deliberate sleep 3s — "so the voice line plays first"
notifyVoice fetch ≤3s
RebuildArchSummary unbounded — spawned bun <generator> generate

~21s consumed before an unbounded subprocess even starts. The hook can't self-report: its fatal path exits 0 and every handler call is individually try/caught, so a harness kill is the only surviving signal.

Three changes:

  1. Inference is off on the SessionEnd path. It catches what grep can't — a doc claiming behaviour the source no longer has — but buying one semantic correction at the risk of losing the whole deterministic pass (the part that actually fixes broken refs and counts) is a bad trade on teardown. DOCINTEGRITY_INFERENCE=1 opts back in for a manual run; non-SessionEnd triggers are unaffected.
  2. The 3s voice-ordering sleep is skipped on SessionEnd — there's no main voice line to sequence against when the session is ending.
  3. RebuildArchSummary's generator subprocess is bounded at 8s with a SIGKILL and a legible log line, so a slow generator reports as a slow generator rather than as an unexplained cancellation.

Measured on a real 5.8MB transcript carrying hook edits, so the cross-ref path was actually reached rather than early-returning:

Case Result
SessionEnd default inference skipped, handler 18ms, hook 265ms
DOCINTEGRITY_INFERENCE=1 inference runs, 6573ms — opt-in intact
8s bound a deliberately 20s generator killed at 8008ms
timer cleanup process exits in 262ms; a leaked timer would have held it ~8s

This part is platform-agnostic — it was found on Linux but nothing in it is Linux-specific.

Overlap with #1591: that PR corrects the stale TRIGGER: Stop hook naming across ten files, including one line each in DocCrossRefIntegrity.ts and RebuildArchSummary.ts. This PR also corrects the DocCrossRefIntegrity header, since the wrong trigger name is what made the failure hard to diagnose. The substance doesn't overlap, but those header lines will conflict trivially. Happy to drop my header change and rebase once #1591 lands — whichever ordering suits you.

2. docs(interceptor) — encode the Linux + Flatpak browser failure class

Four faults hit in one session on Fedora with Chrome installed as a Flatpak. The first symptom looks like a dead extension and isn't, which is what makes this worth documenting rather than leaving each person to rediscover.

  • A daemon socket created inside the sandbox isn't connectable from the host CLI, even though the file is plainly visible. /tmp is genuinely shared both directions, so ls shows the socket while interceptor status says daemon: not running in the same second. The endpoint lives in the sandbox namespace; the visible file is a shadow. The fix is a non-sandboxed browser, not a Flatpak permission tweak.
  • A windowless orphaned Flatpak instance can hold port 19222 invisibly. The operator sees no browser window and reasonably reports none is open, while the sandboxed daemon starves every relay that starts afterwards — including a different browser's. kill on the visible PID doesn't propagate through bwrap; flatpak kill <app-id> does.
  • The relay only attaches to a singleton it spawned itself, and a losing duplicate unlinks the winner's socket on exit. Consequence: CLI calls are self-destructive while another daemon owns the port, so diagnosing the problem re-causes it. (Arguably a bug in its own right — a daemon shouldn't unlink a socket it didn't create. Documented here rather than fixed, since the fix belongs in the daemon.)
  • --load-extension is dead (Chrome 137+, confirmed ignored on 150), so the picker is the only route; in a Flatpak browser it goes through the XDG Document Portal, so /run/flatpak/doc/<id>/... in an error is normal and not the fault. GTK pickers hide dotfolders — Ctrl+H, or the extension directory can't be navigated to at all.

Docs only; no behaviour change.

The SessionEnd hook was being killed by the harness, surfacing only as
"Hook cancelled" with no detail. Adding a timeout bounded the failure but
did not prevent it.

The cause is not one slow call but a serial chain inside the 30s kill
window, on teardown:

  transcript parses x2      ~0.2s   (measured 74ms/pass over 14MB - not the
                                     culprit, despite looking like one)
  deterministic checks      small
  runInferenceAnalysis      <=15s   spawns the `claude` CLI, Sonnet tier;
                                    measured 6573ms on a 10318-char context
  deliberate sleep           3s     "so the voice line plays first"
  notifyVoice fetch         <=3s
  RebuildArchSummary        UNBOUNDED  spawned `bun <generator> generate`

~21s consumed before an unbounded subprocess even started. The hook cannot
self-report: its fatal path exits 0 and every handler call is individually
try/caught, so a harness kill is the only signal that survives.

Three changes:

1. Inference is off on the SessionEnd path. It catches what grep cannot -
   a doc claiming behaviour the source no longer has - but buying one
   semantic correction at the risk of losing the whole deterministic pass,
   which is the part that actually fixes broken refs and counts, is a bad
   trade on teardown. DOCINTEGRITY_INFERENCE=1 opts back in for a manual
   run; non-SessionEnd triggers are unaffected.

2. The 3s voice-ordering sleep is skipped on SessionEnd. There is no main
   voice line to sequence against when the session is ending.

3. RebuildArchSummary's generator subprocess is bounded at 8s with a
   SIGKILL and a legible log line, so a slow generator reports as a slow
   generator rather than as an unexplained cancellation.

Also corrects the DocCrossRefIntegrity header, which still read
"TRIGGER: Stop hook" - a leftover that made this harder to diagnose.

Measured on a real 5.8MB transcript carrying hook edits, so the cross-ref
path was actually reached rather than early-returning:

  SessionEnd default        inference skipped, handler 18ms, hook 265ms
  DOCINTEGRITY_INFERENCE=1  inference runs, 6573ms - opt-in intact
  8s bound                  a deliberately 20s generator killed at 8008ms
  timer cleanup             process exits in 262ms; a leaked timer would
                            have held it ~8s

docs(interceptor): encode the Linux + Flatpak browser failure class

Four faults found bringing Interceptor up on Fedora with Chrome installed
as a Flatpak. The first symptom looks like a dead extension and is not.

1. A daemon socket created INSIDE the sandbox is not connectable from the
   host CLI even though the file is plainly visible. /tmp is genuinely
   shared both directions, so `ls` shows the socket while `interceptor
   status` says "daemon: not running" in the same second. The endpoint
   lives in the sandbox namespace; the visible file is a shadow. The fix is
   a non-sandboxed browser, not a Flatpak permission tweak.

2. A windowless orphaned Flatpak instance can hold the daemon port
   invisibly. The operator sees no browser window and reasonably reports
   none is open, while the sandboxed daemon starves every relay that starts
   afterwards - including a different browser's. `kill` on the visible pid
   does not propagate through bwrap; `flatpak kill <app-id>` does.

3. The relay only attaches to a singleton it spawned itself, and a losing
   duplicate unlinks the winner's socket on exit. Consequence: CLI calls
   are self-destructive while another daemon owns the port, so diagnosing
   the problem re-causes it.

4. --load-extension is dead (Chrome 137+, confirmed ignored on 150), so the
   picker is the only route; in a Flatpak browser it goes through the XDG
   Document Portal, so `/run/flatpak/doc/<id>/...` in an error is normal.
   Dotfolders are hidden in GTK pickers - Ctrl+H, or the operator cannot
   navigate to the extension at all.
@danielmiessler

Copy link
Copy Markdown
Owner

Ported into source with credit — thank you.

The public repo is generated from a private tree by rsync at release time, so a merge here would be reverted at the next cut. Instead the intent landed in source, your handle and this PR number are in the code comment at the fix site, and it ships in the next release. Closing this PR as ported, not rejected.

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