Skip to content

fix(desktop): preserve legacy agent hook notifications#3904

Open
janghoosa wants to merge 2 commits into
superset-sh:mainfrom
janghoosa:janghoosa/create-workspace
Open

fix(desktop): preserve legacy agent hook notifications#3904
janghoosa wants to merge 2 commits into
superset-sh:mainfrom
janghoosa:janghoosa/create-workspace

Conversation

@janghoosa
Copy link
Copy Markdown

@janghoosa janghoosa commented Apr 30, 2026

Description

While the v2 workspace flow is still rolling out, some users may continue using the v1/legacy desktop flow. In that setup, agent lifecycle hooks should still reach the existing desktop hook listener so terminal status updates and completion signals continue to work.

Previously, when SUPERSET_HOST_AGENT_HOOK_URL was present, the notify hook script returned after a successful host-service dispatch. That meant the legacy /hook/complete listener did not receive the same lifecycle event.

This PR keeps the host-service notification path intact, but also preserves dispatch to the existing desktop hook listener.

It also includes workspaceId in the host-service hook payload so lifecycle events are not dropped if the terminal session row is not visible yet.

Related Issues

N/A

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

  • bun test apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts packages/host-service/src/trpc/router/notifications/notifications.test.ts apps/desktop/src/ renderer/routes/_authenticated/components/V2NotificationController/lib/statusTransitions.test.ts apps/desktop/src/renderer/routes/_authenticated/components/ V2NotificationController/lib/resolveV2NotificationTarget.test.ts apps/desktop/src/renderer/stores/v2-notifications/store.test.ts
  • bun run lint

Screenshots (if applicable)

N/A

Additional Notes

This appears to be a regression from e07aef637 / #3675 (feat(desktop): play v2 notification hooks client-side), which added the v2 host-service hook path and exited early after a successful host-service dispatch. That prevented the existing desktop /hook/complete listener from receiving the same lifecycle event.

The regression is present from desktop-v1.6.0 onward.

v2 is not yet comfortable for my workflow because worktree creation and PR number visibility are still being improved there, so I am currently using the v1 flow and hit this hook regression. Once this PR is merged, I would like to keep using v2 more and contribute follow-up improvements for the remaining rough edges I run into.


Summary by cubic

Preserves legacy v1 desktop agent notifications alongside the v2 host-service hook so terminal status and completion keep working. Also includes workspaceId in the v2 payload and falls back to it to avoid dropped events when the terminal session isn’t loaded yet.

  • Bug Fixes
    • Notify script always sends to v2 and also to the legacy /hook/complete when legacy pane/tab ids exist; for pure v2 terminals (no legacy ids), it skips the legacy call to prevent duplicates.
    • V2 host-service hook accepts workspaceId and falls back to it when the terminal session isn’t visible; broadcasts are no longer ignored.
    • Tests assert v2-before-legacy dispatch order and the workspaceId fallback.

Written for commit 5f843f7. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Tests

    • Added tests covering notification behavior, ordering between v2 host-service and legacy hooks, and cases where terminal lookup yields no visible terminal.
  • Chores

    • Notification payloads now include workspace identification and use a sourced fallback when needed.
    • Delivery flow refined to avoid duplicate v2-only notifications while preserving legacy hook delivery.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 752ed934-a7b0-4d66-a5f0-5653d01ccb28

📥 Commits

Reviewing files that changed from the base of the PR and between dba3e04 and 5f843f7.

📒 Files selected for processing (4)
  • apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts
  • apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
  • packages/host-service/src/trpc/router/notifications/notifications.test.ts
  • packages/host-service/src/trpc/router/notifications/notifications.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/host-service/src/trpc/router/notifications/notifications.ts
  • apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts
  • apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh

📝 Walkthrough

Walkthrough

Notification flow now carries an optional workspaceId from the agent script to host-service; the agent script avoids duplicate v2-only notifications and always invokes the legacy /hook/complete unless the pure-v2 guard fires. Host-service prefers terminal-session workspace, falling back to the incoming workspaceId for broadcasts.

Changes

Cohort / File(s) Summary
Agent Setup Notify Hook
apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh, apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts
v2 POST payload now includes workspaceId. Script no longer exits early based on v2 2xx status; it always proceeds to call legacy localhost:/hook/complete unless SUPERSET_HOST_AGENT_HOOK_URL is set and both SUPERSET_PANE_ID and SUPERSET_TAB_ID are empty (pure-v2 guard). Tests updated to assert workspaceId in payload and to verify the host-service curl appears before the legacy curl rather than relying on timing flags.
Host-Service Notifications
packages/host-service/src/trpc/router/notifications/notifications.ts, packages/host-service/src/trpc/router/notifications/notifications.test.ts
Hook input schema accepts optional workspaceId. Handler derives workspaceId by preferring terminalSession.originWorkspaceId and falling back to input.workspaceId. Early-ignore logic now skips broadcasts only when neither source is present. New test covers case where terminal lookup yields no visible row and verifies broadcast includes the payload workspaceId.

Sequence Diagram(s)

sequenceDiagram
    participant AgentScript as Agent Script
    participant HostSvc as Host-Service
    participant DB as DB (terminalSessions)
    participant Broadcaster as EventBus
    participant Legacy as Legacy Listener

    AgentScript->>HostSvc: POST v2 { eventType, terminalId, workspaceId }
    HostSvc->>DB: find terminalSession by terminalId
    DB-->>HostSvc: terminalSession (maybe null)
    HostSvc->>Broadcaster: broadcastAgentLifecycle { eventType, terminalId, workspaceId (derived) }
    AgentScript->>Legacy: POST localhost:/hook/complete (legacy v1)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A workspace ID hops into the stream,
Carried by scripts and caught in the scheme.
If sessions are silent, the payload will say,
"I came with a workspace" — then bounce on its way.
Old and new ears both hear the same beam.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: preserving legacy agent hook notifications alongside the new v2 host-service path.
Description check ✅ Passed The description covers all required template sections: clear change summary, bug fix classification, comprehensive testing information, and additional context about the regression.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 6/8 reviews remaining, refill in 11 minutes and 37 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 30, 2026

Greptile Summary

This PR fixes a regression introduced in #3675 where removing the case 2*) exit 0 guard from notify-hook.template.sh caused the legacy /hook/complete Electron listener to be skipped whenever the v2 host-service dispatch succeeded. It also adds workspaceId to the v2 tRPC payload and uses it as a fallback in notifications.ts when the terminal session row is not yet visible, preventing lifecycle events from being silently dropped during workspace/terminal settling.

Confidence Score: 5/5

Safe to merge; the fix is targeted, well-tested, and the intentional double-dispatch behavior is low-risk in practice.

All findings are P2. The logic of the fix is correct, test coverage is thorough (fallback path, shell script assertions, negative assertion for the removed exit), and the change is a clear regression reversal.

No files require special attention; the only uncertainty is the runtime behavior of the v1 /hook/complete handler when called from a pure-v2 terminal, which is outside the scope of this PR.

Important Files Changed

Filename Overview
apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh Removes early exit after successful v2 host-service dispatch; adds workspaceId to the v2 PAYLOAD; legacy v1 /hook/complete is now always called regardless of v2 outcome
packages/host-service/src/trpc/router/notifications/notifications.ts Adds optional workspaceId to hookInput schema and uses it as a fallback when the terminal session row is not yet in the DB
packages/host-service/src/trpc/router/notifications/notifications.test.ts Adds a test case for the workspaceId fallback when the terminal session row is absent; existing test for unknown terminals still passes
apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts Updates PAYLOAD assertion to include workspaceId; renames and extends test to assert that 2*) exit 0 is absent from the script

Sequence Diagram

sequenceDiagram
    participant Agent as CLI Agent (Claude/Codex)
    participant Script as notify-hook.sh
    participant HS as host-service tRPC (v2)
    participant V1 as Electron /hook/complete (v1)
    participant EB as EventBus / Renderer

    Agent->>Script: lifecycle event (Stop/Start/etc.)

    alt SUPERSET_HOST_AGENT_HOOK_URL is set (v2 env)
        Script->>HS: POST {terminalId, workspaceId, eventType}
        HS->>HS: lookup terminalSession.originWorkspaceId
        Note over HS: fallback to input.workspaceId if row absent (NEW)
        HS->>EB: broadcastAgentLifecycle(workspaceId, eventType)
        Note over Script: No longer exits after v2 success (regression fix)
    end

    Script->>V1: GET /hook/complete?paneId&tabId&sessionId&eventType
    Note over Script,V1: Always dispatched now (was skipped on v2 2xx before)
    V1-->>Script: 200 OK
Loading

Comments Outside Diff (1)

  1. apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh, line 104-134 (link)

    P2 Potential duplicate notifications for v2 users

    With the early exit removed, v2 terminals (those that have SUPERSET_HOST_AGENT_HOOK_URL set) now always reach the legacy /hook/complete call, even when the v2 host-service dispatch succeeds. If the v1 Electron listener is still active and processes the event in a v2 environment, users could receive duplicate ringtones or sidebar indicators — one from the v2 event bus and one from the legacy endpoint.

    Worth confirming that the /hook/complete handler silently ignores requests arriving with empty tabId/sessionId values (likely absent in pure v2 terminals), so v2 users are unaffected in practice.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
    Line: 104-134
    
    Comment:
    **Potential duplicate notifications for v2 users**
    
    With the early exit removed, v2 terminals (those that have `SUPERSET_HOST_AGENT_HOOK_URL` set) now always reach the legacy `/hook/complete` call, even when the v2 host-service dispatch succeeds. If the v1 Electron listener is still active and processes the event in a v2 environment, users could receive duplicate ringtones or sidebar indicators — one from the v2 event bus and one from the legacy endpoint.
    
    Worth confirming that the `/hook/complete` handler silently ignores requests arriving with empty `tabId`/`sessionId` values (likely absent in pure v2 terminals), so v2 users are unaffected in practice.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh:104-134
**Potential duplicate notifications for v2 users**

With the early exit removed, v2 terminals (those that have `SUPERSET_HOST_AGENT_HOOK_URL` set) now always reach the legacy `/hook/complete` call, even when the v2 host-service dispatch succeeds. If the v1 Electron listener is still active and processes the event in a v2 environment, users could receive duplicate ringtones or sidebar indicators — one from the v2 event bus and one from the legacy endpoint.

Worth confirming that the `/hook/complete` handler silently ignores requests arriving with empty `tabId`/`sessionId` values (likely absent in pure v2 terminals), so v2 users are unaffected in practice.

Reviews (1): Last reviewed commit: "fix(desktop): preserve legacy agent hook..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts (1)

28-37: ⚡ Quick win

Strengthen this test to verify behavior instead of one removed string literal.

expect(script).not.toContain("2*) exit 0") can still pass if a different early-exit pattern is introduced. Prefer asserting that the legacy curl call appears after the host-service curl block.

Suggested test hardening
-		expect(script).not.toContain("2*) exit 0");
+		const hostHookIdx = script.indexOf(
+			'curl -sX POST "$SUPERSET_HOST_AGENT_HOOK_URL"',
+		);
+		const legacyHookIdx = script.indexOf(
+			'curl -sG "http://127.0.0.1:${SUPERSET_PORT:-{{DEFAULT_PORT}}}/hook/complete"',
+		);
+		expect(hostHookIdx).toBeGreaterThanOrEqual(0);
+		expect(legacyHookIdx).toBeGreaterThan(hostHookIdx);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts` around lines 28 -
37, The test in notify-hook.test.ts is brittle because it only checks for
absence of a specific string; update the assertion to verify ordering instead:
read the generated script (variable script, from notify-hook.template.sh) and
locate the host-service curl block (e.g. the "$SUPERSET_HOST_AGENT_HOOK_URL"
curl) and the legacy curl call (the "$SUPERSET_HOST_AGENT_HOOK_URL" legacy curl
pattern or legacy URL variable), then assert that the index/position of the
host-service block is less than the index of the legacy curl call (e.g.
expect(script.indexOf(hostServiceSnippet)).toBeLessThan(script.indexOf(legacySnippet))).
This ensures the legacy curl appears after the v2 host-service hook and is
robust to changes in exact early-exit strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts`:
- Around line 28-37: The test in notify-hook.test.ts is brittle because it only
checks for absence of a specific string; update the assertion to verify ordering
instead: read the generated script (variable script, from
notify-hook.template.sh) and locate the host-service curl block (e.g. the
"$SUPERSET_HOST_AGENT_HOOK_URL" curl) and the legacy curl call (the
"$SUPERSET_HOST_AGENT_HOOK_URL" legacy curl pattern or legacy URL variable),
then assert that the index/position of the host-service block is less than the
index of the legacy curl call (e.g.
expect(script.indexOf(hostServiceSnippet)).toBeLessThan(script.indexOf(legacySnippet))).
This ensures the legacy curl appears after the v2 host-service hook and is
robust to changes in exact early-exit strings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f028120-8673-4652-944c-6b2748547146

📥 Commits

Reviewing files that changed from the base of the PR and between 90fed3b and dba3e04.

📒 Files selected for processing (4)
  • apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts
  • apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
  • packages/host-service/src/trpc/router/notifications/notifications.test.ts
  • packages/host-service/src/trpc/router/notifications/notifications.ts

@janghoosa janghoosa force-pushed the janghoosa/create-workspace branch from dba3e04 to 76a05f8 Compare April 30, 2026 09:28
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.

1 participant