fix(desktop): keep agent hook debug logs off the PTY so they can't corrupt TUIs

AV
4mo ago
+47-42

Summary

  • Agent lifecycle hooks (notify-hook, gemini-hook) were writing debug/diagnostic lines to stderr. Hooks run as children of TUI agents (OpenCode, Claude, Gemini) with stdio inherited from the PTY, so those lines printed at the cursor position and corrupted the TUI's diff-rendered screen — interleaved [notify-hook] lines and orphaned text fragments overlaying the UI.
  • All diagnostics now go exclusively to the existing debug_log file channel (/tmp/superset-agent-hooks.log, overridable via SUPERSET_HOOK_DEBUG_LOG). No hook template writes to stdout/stderr anymore (gemini-hook still prints its protocol-required {} JSON to stdout).

Why / Context

notify-hook.template.sh auto-enables debug output when SUPERSET_ENV=development, and its stderr echoes were exact duplicates of the debug_log file writes next to them. In dev builds, every agent lifecycle event (Start/Stop/PermissionRequest) sprayed log lines into the agent's TUI. Diff-rendering TUIs never recover from foreign writes: they repaint only cells they believe changed, so hook text persists as garbage and bottom-row writes desync their screen model by one line. gemini-hook.template.sh additionally had an ungated stderr echo on its v2→v1 fallback path that fired even in production.

How It Works

  • notify-hook.template.sh: dropped the three stderr echoes; the event-received debug_log line now carries the full field set the echo had (adds paneId, workspaceId). The v1 dispatch was duplicated into debug/non-debug branches solely so debug mode could echo the status code — collapsed to a single branch that always captures the status and debug_logs it.
  • gemini-hook.template.sh: the fallback echo now appends to the same log file (unconditional, since it's a rare failure-path signal).
  • New regression test asserts no hook template contains >&2.

Debugging workflow is unchanged in capability: tail -f /tmp/superset-agent-hooks.log instead of reading interleaved (and mangled) lines inside the TUI.

Manual QA

  • Rendered the template ({{MARKER}}/{{DEFAULT_PORT}} substituted) and executed it with a fake Stop payload:
    • with SUPERSET_DEBUG_HOOKS=1: stdout+stderr empty, log file receives the event line (full field set) and dispatch status line
    • without debug: stdout+stderr empty
  • bash -n on both modified templates.
  • Rollout is automatic: createNotifyScript() rewrites the installed notify.sh on next app launch via content comparison (no marker bump needed — hook semantics unchanged, wire payloads identical).

Testing

  • bun test src/main/lib/agent-setup (73 pass, includes new no-stderr regression test)
  • bun run lint
  • bun run typecheck (desktop)

Known Limitations

  • OpenCode's payload fields (hookSessionId/resourceId) parse as empty in the notify hook — pre-existing and unrelated to the logging channel; the Stop event still dispatches. Worth a separate ticket.

Open in Stage

Summary by cubic

Keep agent hook debug output off the PTY to stop corrupting diff-rendered TUIs. All diagnostics now go to the hook debug log file instead of stdout/stderr.

  • Bug Fixes
    • Removed all stderr echoes from notify-hook.template.sh and gemini-hook.template.sh; diagnostics append to ${SUPERSET_HOOK_DEBUG_LOG:-/tmp/superset-agent-hooks.log}. Gemini still prints only the required JSON to stdout.
    • Simplified notify-hook v1 dispatch into a single path and logged status with more context fields (paneId, workspaceId, etc.).
    • Added a regression test to ensure no hook template writes to stderr (>&2) and updated expectations for the notify script content.

Summary by CodeRabbit

  • Bug Fixes
    • Prevented agent hook diagnostics from appearing unexpectedly in terminal output or interfering with command-line protocols.
    • Improved logging for host-service dispatch failures, including response status details and event context.
    • Preserved fallback behavior when host-service delivery is unavailable, while recording failures in the debug log.