Skip to content

Set up hooks for notifications#121

Merged
Kitenite merged 4 commits intomainfrom
set-up-hooks-for-notifications
Nov 21, 2025
Merged

Set up hooks for notifications#121
Kitenite merged 4 commits intomainfrom
set-up-hooks-for-notifications

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Nov 21, 2025

  • WIP - kinda works, cursor hook not working, need to test integration with terminals + ensure wrapper works
  • WIP - almost done
  • WIP - kinda working

Description

Related Issues

Type of Change

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

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

  • New Features

    • Real-time notifications for agent completions with optional focus actions
    • Visual attention indicators (red ping badges) on tabs and workspaces
    • Terminal now carries workspace and tab context for better integration
    • Renderer listens for agent hooks to surface events in the UI
  • Bug Fixes

    • Fixed stray newline/formatting artifact in documentation
  • Chores

    • Added runtime dependencies and local notification server setup

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 21, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Introduces a local notifications subsystem: an Express webhook server, a tRPC notifications router with subscription streaming, agent CLI hook setup and wrappers, terminal environment propagation, renderer listeners to mark tabs needing attention, and UI badges for attention state.

Changes

Cohort / File(s) Summary
Docs & Manifest
apps/desktop/CLAUDE.md, apps/desktop/package.json, package.json, apps/desktop/src/shared/constants.ts
Fixed markdown newline in CLAUDE.md; added express and @types/express to app manifest; added NOTIFICATIONS_PORT = 31415; package.json fields adjusted.
Notifications server
apps/desktop/src/main/lib/notifications/server.ts, apps/desktop/src/main/windows/main.ts
Added Express notification server (GET /hook/complete, /health), exported notificationsEmitter and notificationsApp, started/stopped server in main window, and show system notifications that emit focus-tab events.
tRPC router
apps/desktop/src/lib/trpc/routers/notifications.ts, apps/desktop/src/lib/trpc/routers/index.ts
Added createNotificationsRouter() exposing subscribe streaming "agent-complete" and "focus-tab" events; wired into AppRouter.
Agent hook setup
apps/desktop/src/main/lib/agent-setup.ts, apps/desktop/src/main/index.ts
New setupAgentHooks() creating ~/.superset structure, notify.sh hook, and CLI wrapper scripts; invoked (guarded) during main initialization.
Terminal env & router input
apps/desktop/src/main/lib/terminal-manager.ts, apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
createOrAttach now accepts tabTitle and workspaceName; terminal spawn env enriched with SUPERSET_TAB_ID/TITLE/WORKSPACE_NAME/WORKSPACE_ID/PORT and PATH from superset bin dir.
Renderer tabs store & hooks
apps/desktop/src/renderer/stores/tabs/types.ts, apps/desktop/src/renderer/stores/tabs/store.ts, apps/desktop/src/renderer/stores/tabs/hooks.ts, apps/desktop/src/renderer/stores/tabs/index.ts, apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts, apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts
Added needsAttention?: boolean to BaseTab, setNeedsAttention action and hook, useAgentHookListener subscribing to tRPC notifications, and activation handler clears needsAttention.
Renderer UI & terminal usage
apps/desktop/src/renderer/screens/main/index.tsx, apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
Invoke useAgentHookListener; render red attention ping for workspaces/tabs with needsAttention; pass tabTitle into terminal create/attach flows.

Sequence Diagram(s)

sequenceDiagram
    participant Agent as External Agent
    participant NotifServer as Notifications Server (Express)
    participant Main as Main Process (Emitter)
    participant TRPC as tRPC Notifications
    participant Renderer as Renderer (useAgentHookListener)
    participant Store as Tabs Store

    Agent->>NotifServer: GET /hook/complete
    NotifServer->>Main: Emit "agent-complete" via notificationsEmitter
    Main->>TRPC: Emit/bridge event for subscribers
    TRPC->>Renderer: Notification subscription delivers "agent-complete"
    alt tab inactive
        Renderer->>Store: setNeedsAttention(tabId, true)
        Store->>Renderer: UI shows attention badge
    end
    Main->>Main: On notification click -> emit "focus-tab"
    TRPC->>Renderer: "focus-tab" delivered
    Renderer->>Renderer: setActiveWorkspace -> setActiveTab(tabId)
    Renderer->>Store: Clear needsAttention(tabId)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–25 minutes

  • Review agent-setup script file operations and wrapper safety.
  • Verify notificationsEmitter lifecycle and server start/stop in main window.
  • Confirm tRPC subscription teardown and event mapping.
  • Validate terminal spawn env propagation and interactions with wrappers/store.

Possibly related PRs

Poem

🐰 I tunneled a hook beneath ~/.superset,

a tiny notify sh for agents set—
When tasks are done my nose will twitch,
Tabs will ping and windows switch,
Hooray: the rabbit cheers, attention met.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete with only WIP notes and placeholder sections unfilled. No substantive information is provided about changes, related issues, testing, or other required sections. Complete the description by filling in the Description section with details about the notification hook implementation, mark the appropriate Type of Change, describe testing steps, and remove or consolidate the WIP notes into structured sections.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Set up hooks for notifications' directly relates to the main objective of the changeset, which implements a notification hook system with agent completion hooks and related infrastructure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 908566a and 7934472.

📒 Files selected for processing (7)
  • apps/desktop/CLAUDE.md (1 hunks)
  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts (3 hunks)
  • apps/desktop/src/main/index.ts (2 hunks)
  • apps/desktop/src/main/lib/agent-setup.ts (1 hunks)
  • apps/desktop/src/main/lib/terminal-manager.ts (5 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (3 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (4 hunks)

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

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.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)

6-17: Add tabTitle to the useEffect dependency array to prevent stale closure.

The restartTerminal function (line 95) captures tabTitle and uses it when restarting after process exit (line 103). If a tab is renamed while the terminal is running, the effect won't re-run since tabTitle is missing from the dependency array, causing the old title to be used on restart.

Update line 166:

-	}, [tabId, workspaceId, setActiveTab]);
+	}, [tabId, workspaceId, tabTitle, setActiveTab]);
🧹 Nitpick comments (5)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (1)

38-43: Optimize needsAttention computation with useMemo.

The needsAttention computation runs on every render, filtering all tabs each time. With many tabs, this could impact performance. Consider memoizing this derived value to avoid unnecessary recalculations.

Apply this diff to optimize the computation:

+import { useMemo } from "react";
+
 	const tabs = useTabs();

 	// Derive if workspace needs attention from any of its tabs
-	const needsAttention = tabs
-		.filter((t) => t.workspaceId === id)
-		.some((t) => t.needsAttention);
+	const needsAttention = useMemo(
+		() => tabs
+			.filter((t) => t.workspaceId === id)
+			.some((t) => t.needsAttention),
+		[tabs, id]
+	);
apps/desktop/src/main/windows/main.ts (1)

79-82: Improve cleanup robustness.

The server cleanup could fail silently, and removeAllListeners() is overly broad.

Apply this diff to improve cleanup:

 	window.on("close", () => {
-		server.close();
-		notificationsEmitter.removeAllListeners();
+		server.close((err) => {
+			if (err) {
+				console.error("[notifications] Error closing server:", err);
+			}
+		});
+		notificationsEmitter.removeAllListeners("agent-complete");
+		notificationsEmitter.removeAllListeners("focus-tab");
 	});
apps/desktop/src/lib/trpc/routers/terminal/terminal.ts (1)

7-21: Tab/workspace context propagation into terminals looks consistent.

The new tabTitle input and derived workspaceName are correctly threaded into terminalManager.createOrAttach, and the DB lookup for cwd/workspaceName has sane fallbacks. The behavior of createOrAttach should remain backward-compatible while enriching the terminal env.

One small suggestion: consider moving or referencing the “IMPORTANT: env vars” doc from here to where the env is actually assembled (in TerminalManager) to avoid drift between comments and implementation over time.

Also applies to: 27-41, 51-59

apps/desktop/src/main/lib/notifications/server.ts (1)

16-24: Tighten HTTP semantics and exposure for the hooks endpoint.

Functionally this works, but two things are worth tightening:

  1. /hook/complete performs a side-effect (emitting agent-complete) via GET. Using POST for this hook would better match HTTP semantics and leaves room for a JSON body if you ever extend the payload.

  2. Since this is a generic, unauthenticated CORS-* endpoint, please ensure that wherever notificationsApp.listen is called, it’s bound to 127.0.0.1 (or otherwise firewalled) so the port isn’t exposed to the local network unnecessarily.

Also applies to: 27-44

apps/desktop/src/main/lib/agent-setup.ts (1)

54-96: Consider adding idempotency checks for wrapper creation.

The wrappers are recreated on every app startup even if they haven't changed. Consider checking if the wrapper already exists and has the correct content before rewriting it.

This would improve startup performance and reduce unnecessary disk I/O:

function createClaudeWrapper(): void {
	const wrapperPath = path.join(BIN_DIR, "claude");
	const realClaude = findRealBinary("claude");
	const notifyPath = path.join(HOOKS_DIR, "notify.sh");

	if (!realClaude) {
		console.log("[agent-setup] Claude not found, skipping wrapper");
		return;
	}

	const script = `#!/bin/bash
# Superset wrapper for Claude Code
# Injects notification hook settings

SUPERSET_CLAUDE_SETTINGS='{"hooks":{"Stop":[{"matcher":"","hooks":[{"type":"command","command":"${notifyPath}"}]}]}}'

exec "${realClaude}" --settings "$SUPERSET_CLAUDE_SETTINGS" "$@"
`;

	// Only write if content has changed
	if (fs.existsSync(wrapperPath)) {
		const existing = fs.readFileSync(wrapperPath, "utf-8");
		if (existing === script) {
			console.log(`[agent-setup] Claude wrapper unchanged, skipping`);
			return;
		}
	}

	fs.writeFileSync(wrapperPath, script, { mode: 0o755 });
	console.log(`[agent-setup] Created Claude wrapper -> ${realClaude}`);
}

Apply similar logic to createCodexWrapper and createNotifyScript.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0265260 and 908566a.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • apps/desktop/CLAUDE.md (1 hunks)
  • apps/desktop/package.json (2 hunks)
  • apps/desktop/src/lib/trpc/routers/index.ts (2 hunks)
  • apps/desktop/src/lib/trpc/routers/notifications.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts (3 hunks)
  • apps/desktop/src/main/index.ts (2 hunks)
  • apps/desktop/src/main/lib/agent-setup.ts (1 hunks)
  • apps/desktop/src/main/lib/notifications/server.ts (1 hunks)
  • apps/desktop/src/main/lib/terminal-manager.ts (3 hunks)
  • apps/desktop/src/main/windows/main.ts (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (3 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (4 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/index.tsx (2 hunks)
  • apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts (2 hunks)
  • apps/desktop/src/renderer/stores/tabs/hooks.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/index.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/types.ts (2 hunks)
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1 hunks)
  • apps/desktop/src/shared/constants.ts (1 hunks)
  • package.json (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
apps/desktop/src/renderer/screens/main/index.tsx (1)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1)
  • useAgentHookListener (9-41)
apps/desktop/src/main/index.ts (1)
apps/desktop/src/main/lib/agent-setup.ts (1)
  • setupAgentHooks (102-115)
apps/desktop/src/shared/constants.ts (1)
apps/desktop/src/main/lib/notifications/server.ts (1)
  • NOTIFICATIONS_PORT (57-57)
apps/desktop/src/lib/trpc/routers/terminal/terminal.ts (1)
apps/desktop/src/main/lib/db/index.ts (1)
  • db (19-26)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useTabsStore (35-149)
apps/desktop/src/renderer/stores/tabs/hooks.ts (1)
apps/desktop/src/renderer/stores/tabs/store.ts (1)
  • useTabsStore (35-149)
apps/desktop/src/main/lib/terminal-manager.ts (3)
apps/desktop/src/main/lib/agent-setup.ts (1)
  • getSupsersetPath (120-122)
apps/desktop/src/main/lib/notifications/server.ts (1)
  • NOTIFICATIONS_PORT (57-57)
apps/desktop/src/shared/constants.ts (1)
  • NOTIFICATIONS_PORT (11-11)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)
apps/desktop/src/renderer/stores/tabs/hooks.ts (1)
  • useTabs (3-3)
apps/desktop/src/lib/trpc/routers/notifications.ts (2)
apps/desktop/src/main/lib/notifications/server.ts (2)
  • AgentCompleteEvent (5-10)
  • notificationsEmitter (12-12)
apps/desktop/src/lib/trpc/index.ts (1)
  • router (15-15)
apps/desktop/src/main/windows/main.ts (2)
apps/desktop/src/main/lib/notifications/server.ts (4)
  • notificationsApp (56-56)
  • NOTIFICATIONS_PORT (57-57)
  • notificationsEmitter (12-12)
  • AgentCompleteEvent (5-10)
apps/desktop/src/shared/constants.ts (1)
  • NOTIFICATIONS_PORT (11-11)
apps/desktop/src/lib/trpc/routers/index.ts (1)
apps/desktop/src/lib/trpc/routers/notifications.ts (1)
  • createNotificationsRouter (12-37)
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (1)
apps/desktop/src/renderer/stores/tabs/hooks.ts (1)
  • useTabs (3-3)
🔇 Additional comments (18)
apps/desktop/CLAUDE.md (1)

96-96: LGTM!

The code block formatting has been corrected. This is a documentation-only change with no functional impact.

package.json (1)

4-46: LGTM!

These fields are standard monorepo configuration. The repository metadata, devDependencies, packageManager specification, type module declaration, and workspace definitions are appropriate for this setup.

apps/desktop/src/renderer/stores/tabs/index.ts (1)

5-5: LGTM!

The new export follows the existing pattern and properly exposes the useAgentHookListener hook.

apps/desktop/src/renderer/stores/tabs/hooks.ts (1)

23-24: LGTM!

The hook follows the established pattern and properly exposes the setNeedsAttention action from the store.

apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItem.tsx (1)

99-104: LGTM!

The attention indicator implementation is clean and follows the same pattern used in TabItem. The visual feedback (animated ping + solid indicator) effectively draws attention to workspaces that need it.

apps/desktop/package.json (1)

39-50: I'll search the codebase to verify if the notification server implementation uses any Express patterns that might break with v5.

Based on my verification of Express 5.1.0:

Express 5.1.0 is stable, secure, and recommended for production use.

Express 5 is tagged as "latest" on npm, and the latest version 5.1.0 is confirmed as non-vulnerable by Snyk. CVE-2024-45590 has been patched in body-parser version 1.20.3, which is included in Express 5.1.0.

Regarding breaking changes: Express 5 maintains the same basic API as Express 4, though breaking changes exist that could affect applications built with Express 4. The primary breaking changes involve deprecated method signatures removed since v4.11.0, route matching updates via path-to-regexp 8.x (which improves security and mitigates ReDoS vulnerabilities), and automatic promise rejection handling in async middleware.

The app should run your test suite after the upgrade to catch any incompatibilities with the notification server implementation, as the review suggests. These are standard migration checks rather than red flags.

apps/desktop/src/renderer/screens/main/index.tsx (1)

6-6: LGTM! Clean integration of the notification listener.

The hook is properly placed and will establish the subscription when the main screen mounts.

Also applies to: 22-23

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1)

152-160: LGTM! Attention indicator is well-implemented.

The pinging animation provides clear visual feedback, and the title attribute aids accessibility.

apps/desktop/src/renderer/stores/tabs/store.ts (1)

133-140: LGTM! The implementation correctly updates the tab state.

The action maps over the tabs array to update the matching tab while preserving immutability.

apps/desktop/src/renderer/stores/tabs/types.ts (2)

14-14: LGTM! Type definition is appropriate.

The optional needsAttention flag is well-suited for tracking attention state.


65-65: LGTM! Method signature is clear and consistent.

The setNeedsAttention method signature aligns with the store implementation.

apps/desktop/src/renderer/stores/tabs/helpers/active-tab.ts (1)

19-24: LGTM! Clearing attention on activation is the correct behavior.

When a tab becomes active, its attention indicator should be dismissed. The implementation correctly updates the tabs array and includes it in the returned state.

Also applies to: 35-35

apps/desktop/src/lib/trpc/routers/index.ts (1)

3-3: LGTM! Router integration follows the established pattern.

The notifications router is properly wired into the main AppRouter.

Also applies to: 19-19

apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1)

27-37: LGTM! Focus-tab handling uses proper async workflow.

The workspace switch is performed first with a mutation, and tab activation occurs in the success callback. This ensures proper sequencing.

apps/desktop/src/lib/trpc/routers/notifications.ts (1)

1-36: Notifications subscription wiring looks solid.

The NotificationEvent union, dual listeners on notificationsEmitter, and cleanup via off on unsubscribe are all consistent and should behave well with TRPC subscriptions. I don’t see issues in this router as written.

apps/desktop/src/main/lib/agent-setup.ts (3)

1-8: LGTM: Imports and directory structure are well-defined.

The Node.js built-in imports are appropriate, and the directory structure under ~/.superset is cleanly organized.


78-96: LGTM: Codex wrapper correctly uses resolved paths.

Unlike the Claude wrapper, this correctly uses path.join() to construct the notify script path and references it via a variable in the generated script.


127-129: LGTM: Simple getter function.

The function correctly returns the bin directory path.

Comment thread apps/desktop/src/main/index.ts Outdated
Comment thread apps/desktop/src/main/lib/agent-setup.ts
Comment on lines +32 to +49
function createNotifyScript(): void {
const notifyPath = path.join(HOOKS_DIR, "notify.sh");
const script = `#!/bin/bash
# Superset agent notification hook
# Called by CLI agents (Claude Code, Codex, etc.) when they complete

# Only run if inside a Superset terminal
[ -z "$SUPERSET_TAB_ID" ] && exit 0

curl -sG "http://127.0.0.1:\${SUPERSET_PORT:-31415}/hook/complete" \\
--data-urlencode "tabId=$SUPERSET_TAB_ID" \\
--data-urlencode "tabTitle=$SUPERSET_TAB_TITLE" \\
--data-urlencode "workspaceName=$SUPERSET_WORKSPACE_NAME" \\
--data-urlencode "workspaceId=$SUPERSET_WORKSPACE_ID" \\
> /dev/null 2>&1
`;
fs.writeFileSync(notifyPath, script, { mode: 0o755 });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use NOTIFICATIONS_PORT constant for consistency.

The port is hardcoded as 31415 in the notify script, but the AI summary indicates that NOTIFICATIONS_PORT is defined in apps/desktop/src/shared/constants.ts. This creates a potential mismatch if the port constant differs from the hardcoded value.

Consider passing the NOTIFICATIONS_PORT value into the script generation:

-function createNotifyScript(): void {
+function createNotifyScript(port: number): void {
 	const notifyPath = path.join(HOOKS_DIR, "notify.sh");
 	const script = `#!/bin/bash
 # Superset agent notification hook
 # Called by CLI agents (Claude Code, Codex, etc.) when they complete
 
 # Only run if inside a Superset terminal
 [ -z "$SUPERSET_TAB_ID" ] && exit 0
 
-curl -sG "http://127.0.0.1:\${SUPERSET_PORT:-31415}/hook/complete" \\
+curl -sG "http://127.0.0.1:\${SUPERSET_PORT:-${port}}/hook/complete" \\
   --data-urlencode "tabId=$SUPERSET_TAB_ID" \\
   --data-urlencode "tabTitle=$SUPERSET_TAB_TITLE" \\
   --data-urlencode "workspaceName=$SUPERSET_WORKSPACE_NAME" \\
   --data-urlencode "workspaceId=$SUPERSET_WORKSPACE_ID" \\
   > /dev/null 2>&1
 `;
 	fs.writeFileSync(notifyPath, script, { mode: 0o755 });
 }

Then import and pass NOTIFICATIONS_PORT when calling this function.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/desktop/src/main/lib/agent-setup.ts around lines 32 to 49 the notify
script hardcodes port 31415; update to use the NOTIFICATIONS_PORT constant
instead by importing NOTIFICATIONS_PORT from
apps/desktop/src/shared/constants.ts, change createNotifyScript to accept a port
parameter (or read the imported constant inside the function), interpolate that
port into the curl URL (replacing 31415), and update all call sites to pass the
constant so the script and constant remain consistent.

Comment on lines +54 to +73
function createClaudeWrapper(): void {
const wrapperPath = path.join(BIN_DIR, "claude");
const realClaude = findRealBinary("claude");

if (!realClaude) {
console.log("[agent-setup] Claude not found, skipping wrapper");
return;
}

const script = `#!/bin/bash
# Superset wrapper for Claude Code
# Injects notification hook settings

SUPERSET_CLAUDE_SETTINGS='{"hooks":{"Stop":[{"matcher":"","hooks":[{"type":"command","command":"~/.superset/hooks/notify.sh"}]}]}}'

exec "${realClaude}" --settings "$SUPERSET_CLAUDE_SETTINGS" "$@"
`;
fs.writeFileSync(wrapperPath, script, { mode: 0o755 });
console.log(`[agent-setup] Created Claude wrapper -> ${realClaude}`);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use proper path construction instead of hardcoding ~/.superset.

The hook path is hardcoded as ~/.superset/hooks/notify.sh in the generated script, which may not work if the home directory path contains special characters or in edge cases where ~ doesn't expand properly.

Apply this diff to use the actual resolved path:

 function createClaudeWrapper(): void {
 	const wrapperPath = path.join(BIN_DIR, "claude");
 	const realClaude = findRealBinary("claude");
+	const notifyPath = path.join(HOOKS_DIR, "notify.sh");
 
 	if (!realClaude) {
 		console.log("[agent-setup] Claude not found, skipping wrapper");
 		return;
 	}
 
 	const script = `#!/bin/bash
 # Superset wrapper for Claude Code
 # Injects notification hook settings
 
-SUPERSET_CLAUDE_SETTINGS='{"hooks":{"Stop":[{"matcher":"","hooks":[{"type":"command","command":"~/.superset/hooks/notify.sh"}]}]}}'
+SUPERSET_CLAUDE_SETTINGS='{"hooks":{"Stop":[{"matcher":"","hooks":[{"type":"command","command":"${notifyPath}"}]}]}}'
 
 exec "${realClaude}" --settings "$SUPERSET_CLAUDE_SETTINGS" "$@"
 `;
 	fs.writeFileSync(wrapperPath, script, { mode: 0o755 });
 	console.log(`[agent-setup] Created Claude wrapper -> ${realClaude}`);
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/desktop/src/main/lib/agent-setup.ts around lines 54 to 73, the generated
Claude wrapper hardcodes the hook as "~/.superset/hooks/notify.sh" which can
fail because "~" may not expand; resolve the actual user home and build the full
hook path instead (e.g. use Node's os.homedir() or process.env.HOME and
path.join to construct the superset hooks path), inject that resolved, properly
escaped/quoted absolute path into the script string, and write the wrapper with
the computed path so the wrapper calls the real notify.sh reliably across
environments.

Comment on lines +102 to +115
export function setupAgentHooks(): void {
console.log("[agent-setup] Initializing agent hooks...");

// Create directories
fs.mkdirSync(BIN_DIR, { recursive: true });
fs.mkdirSync(HOOKS_DIR, { recursive: true });

// Create scripts
createNotifyScript();
createClaudeWrapper();
createCodexWrapper();

console.log("[agent-setup] Agent hooks initialized");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for filesystem operations.

The function performs multiple filesystem operations without error handling. If directory creation or script writing fails (e.g., due to permissions), the app will crash on startup.

Apply this diff to add basic error handling:

 export function setupAgentHooks(): void {
 	console.log("[agent-setup] Initializing agent hooks...");
 
-	// Create directories
-	fs.mkdirSync(BIN_DIR, { recursive: true });
-	fs.mkdirSync(HOOKS_DIR, { recursive: true });
-
-	// Create scripts
-	createNotifyScript();
-	createClaudeWrapper();
-	createCodexWrapper();
-
-	console.log("[agent-setup] Agent hooks initialized");
+	try {
+		// Create directories
+		fs.mkdirSync(BIN_DIR, { recursive: true });
+		fs.mkdirSync(HOOKS_DIR, { recursive: true });
+
+		// Create scripts
+		createNotifyScript();
+		createClaudeWrapper();
+		createCodexWrapper();
+
+		console.log("[agent-setup] Agent hooks initialized");
+	} catch (error) {
+		console.error("[agent-setup] Failed to initialize agent hooks:", error);
+		// Optionally: decide if this should be a fatal error or just log and continue
+	}
 }
🤖 Prompt for AI Agents
In apps/desktop/src/main/lib/agent-setup.ts around lines 102 to 115, the
filesystem operations (fs.mkdirSync and the create* script calls) lack error
handling and can crash the app on permission or I/O failures; wrap the directory
creation and script-creation calls in a try/catch (either per-operation or a
single block) and on error log a clear message with the caught error
(console.error or a logger) and terminate gracefully (process.exit(1)) or
rethrow to surface the failure; ensure any synchronous operations remain
synchronous inside the try/catch and preserve the existing success log only when
no error occurred.

Comment thread apps/desktop/src/main/lib/terminal-manager.ts Outdated
Comment on lines +47 to +50
// Start notifications HTTP server
const server = notificationsApp.listen(NOTIFICATIONS_PORT, "127.0.0.1", () => {
console.log(`[notifications] Listening on http://127.0.0.1:${NOTIFICATIONS_PORT}`);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for the notifications server.

The server starts without error handling, which could cause unhandled exceptions if the port is already in use or if there are other network issues.

Apply this diff to add error handling:

 	// Start notifications HTTP server
-	const server = notificationsApp.listen(NOTIFICATIONS_PORT, "127.0.0.1", () => {
-		console.log(`[notifications] Listening on http://127.0.0.1:${NOTIFICATIONS_PORT}`);
-	});
+	const server = notificationsApp.listen(NOTIFICATIONS_PORT, "127.0.0.1", () => {
+		console.log(`[notifications] Listening on http://127.0.0.1:${NOTIFICATIONS_PORT}`);
+	});
+
+	server.on("error", (error) => {
+		console.error("[notifications] Server error:", error);
+	});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Start notifications HTTP server
const server = notificationsApp.listen(NOTIFICATIONS_PORT, "127.0.0.1", () => {
console.log(`[notifications] Listening on http://127.0.0.1:${NOTIFICATIONS_PORT}`);
});
// Start notifications HTTP server
const server = notificationsApp.listen(NOTIFICATIONS_PORT, "127.0.0.1", () => {
console.log(`[notifications] Listening on http://127.0.0.1:${NOTIFICATIONS_PORT}`);
});
server.on("error", (error) => {
console.error("[notifications] Server error:", error);
});
🤖 Prompt for AI Agents
In apps/desktop/src/main/windows/main.ts around lines 47 to 50, the
notifications HTTP server is started without any error handling; add a server
'error' event listener after calling notificationsApp.listen to catch startup
errors (e.g., EADDRINUSE) and log them with context, then exit or retry as
appropriate; specifically, attach server.on('error', (err) => { handle known
codes like EADDRINUSE with a clear console.error message and process.exit(1) } )
and ensure the existing listen callback remains for successful start logging.

Comment on lines +53 to +73
notificationsEmitter.on("agent-complete", (event: AgentCompleteEvent) => {
if (Notification.isSupported()) {
const notification = new Notification({
title: `Agent Complete — ${event.workspaceName}`,
body: `"${event.tabTitle}" has finished its task`,
silent: false,
});

notification.on("click", () => {
window.show();
window.focus();
// Request focus on the specific tab
notificationsEmitter.emit("focus-tab", {
tabId: event.tabId,
workspaceId: event.workspaceId,
});
});

notification.show();
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Verify window validity before interaction and add error handling.

The notification click handler interacts with the window without verifying it's still valid. Additionally, notification creation could throw exceptions.

Apply this diff to add safety checks:

 	// Handle agent completion notifications
 	notificationsEmitter.on("agent-complete", (event: AgentCompleteEvent) => {
+		if (window.isDestroyed()) return;
+		
 		if (Notification.isSupported()) {
+			try {
 				const notification = new Notification({
 					title: `Agent Complete — ${event.workspaceName}`,
 					body: `"${event.tabTitle}" has finished its task`,
 					silent: false,
 				});
 
 				notification.on("click", () => {
+					if (window.isDestroyed()) return;
 					window.show();
 					window.focus();
 					// Request focus on the specific tab
 					notificationsEmitter.emit("focus-tab", {
 						tabId: event.tabId,
 						workspaceId: event.workspaceId,
 					});
 				});
 
 				notification.show();
+			} catch (error) {
+				console.error("[notifications] Failed to show notification:", error);
+			}
 		}
 	});

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/desktop/src/main/windows/main.ts around lines 53 to 73, the notification
click handler currently interacts with the BrowserWindow without checking that
it still exists or is not destroyed and the Notification constructor can throw;
wrap the Notification creation and show in a try/catch to handle/ log creation
errors, and inside the "click" handler first verify the window reference is not
null/undefined and !window.isDestroyed() (optionally check !window.isDestroyed()
&& window.webContents) before calling show(), focus(), or emitting "focus-tab";
if the window is invalid, skip those calls and log or noop to avoid exceptions.

Comment on lines +9 to +26
export function useAgentHookListener() {
const setActiveWorkspace = useSetActiveWorkspace();
const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery();

trpc.notifications.subscribe.useSubscription(undefined, {
onData: (event) => {
if (event.type === "agent-complete") {
const { tabId, workspaceId } = event.data;
const state = useTabsStore.getState();

// Only show red dot if not already viewing this tab
const isAlreadyActive =
activeWorkspace?.id === workspaceId &&
state.activeTabIds[workspaceId] === tabId;

if (!isAlreadyActive) {
state.setNeedsAttention(tabId, true);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential race condition with workspace state.

The activeWorkspace data comes from a React Query result, which may be stale when the subscription event fires. This could cause the attention indicator to show or hide incorrectly.

Consider reading the active workspace directly from a store or ensuring the query data is fresh:

 export function useAgentHookListener() {
 	const setActiveWorkspace = useSetActiveWorkspace();
-	const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery();
 
 	trpc.notifications.subscribe.useSubscription(undefined, {
 		onData: (event) => {
 			if (event.type === "agent-complete") {
 				const { tabId, workspaceId } = event.data;
 				const state = useTabsStore.getState();
 
-				// Only show red dot if not already viewing this tab
-				const isAlreadyActive =
-					activeWorkspace?.id === workspaceId &&
-					state.activeTabIds[workspaceId] === tabId;
+				// Get fresh workspace state at event time
+				// TODO: If activeWorkspace is stored elsewhere, retrieve it here
+				// For now, check only tab activeness within the workspace
+				const isAlreadyActive = state.activeTabIds[workspaceId] === tabId;
 
 				if (!isAlreadyActive) {
 					state.setNeedsAttention(tabId, true);
 				}
 			}

Note: This assumes you want to show attention for inactive tabs regardless of workspace. If workspace check is needed, consider storing active workspace ID in a Zustand store for synchronous access.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts around lines
9-26, the subscription handler uses React Query's activeWorkspace (which can be
stale) to decide whether to mark a tab as needing attention; replace that
reactive query access with a synchronous read of the workspace ID from a local
store (e.g. call the zustand store getter like
useWorkspacesStore.getState().activeWorkspaceId) so the comparison with
workspaceId is always current; if you intend to ignore workspace context
entirely, remove the workspace check and always mark non-active tabs, otherwise
ensure you import/use the correct workspace store getter and use it
synchronously in the onData handler before calling state.setNeedsAttention.

IS_LINUX: process.platform === "linux",
};

export const NOTIFICATIONS_PORT = 31415;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Consider port collision handling.

Port 31415 is in the registered port range. While unlikely to conflict, the notification server should gracefully handle the case where this port is already in use by another service on the user's machine. This is particularly important for a desktop application that users might run alongside other development tools.

Verify that the notification server implementation (in apps/desktop/src/main/lib/notifications/server.ts) includes error handling for EADDRINUSE errors when binding to this port.


🏁 Script executed:

#!/bin/bash
# Description: Check if notification server handles port conflicts

# Look for error handling in the notification server
ast-grep --pattern $'app.listen($$$, () => {
  $$$
})'

# Also check for error event handlers
rg -A 5 "EADDRINUSE|address already in use" apps/desktop/src/main/lib/notifications/

Length of output: 0


I need to broaden the verification approach, as the initial script may have had pattern matching issues. Let me search more comprehensively for the notification server and its error handling.


🏁 Script executed:

#!/bin/bash
# Step 1: Verify the notification server file exists
echo "=== Checking if notification server file exists ==="
fd -e ts notification || echo "File not found with fd"
find apps/desktop -name "*notification*" -o -name "*server*" 2>/dev/null | head -20

echo ""
echo "=== Searching for port binding patterns ==="
# Search for listen, bind, or server creation with broader patterns
rg -n "listen|bind|createServer" apps/desktop/src/main --type=ts -C 3

echo ""
echo "=== Searching for error handling in notifications directory ==="
rg -n "error|catch|Error" apps/desktop/src/main/lib/notifications --type=ts -C 2

Length of output: 3228


🏁 Script executed:

#!/bin/bash
# Step 1: Verify the notification server file exists and explore structure
echo "=== Finding notification-related files ==="
fd "notification" apps/desktop/src/main --type f

echo ""
echo "=== Check if server.ts exists at expected path ==="
ls -la apps/desktop/src/main/lib/notifications/server.ts 2>/dev/null || echo "File not found at expected path"

echo ""
echo "=== Broader search for port 31415 or NOTIFICATIONS_PORT usage ==="
rg "31415|NOTIFICATIONS_PORT" apps/desktop/src --type=ts -B 2 -A 5

echo ""
echo "=== Search for error handlers on server/listener objects ==="
rg -n "\.on\s*\(\s*['\"]error['\"]|\.on\s*\(\s*['\"]EADDRINUSE" apps/desktop/src/main --type=ts -C 3

Length of output: 4450


I need to examine the exact code where the notification server is started to confirm the absence of error handling.


🏁 Script executed:

#!/bin/bash
# Get exact code context around the server.listen call in main.ts
echo "=== Exact code in main.ts around notification server binding ==="
sed -n '45,60p' apps/desktop/src/main/windows/main.ts

echo ""
echo "=== Check if there's error handling AFTER the listen call ==="
sed -n '48,80p' apps/desktop/src/main/windows/main.ts

Length of output: 1579


Add error handling for port binding in notification server initialization.

The notification server at apps/desktop/src/main/windows/main.ts (line 48) does not handle EADDRINUSE errors when binding to port 31415. If this port is already in use, the application startup will fail without graceful error handling. Add an error event listener to the server object, similar to the pattern used in apps/desktop/src/main/lib/port-manager.ts:

const server = notificationsApp.listen(NOTIFICATIONS_PORT, "127.0.0.1", () => {
	console.log(`[notifications] Listening on http://127.0.0.1:${NOTIFICATIONS_PORT}`);
});

server.on("error", (err: NodeJS.ErrnoException) => {
	if (err.code === "EADDRINUSE") {
		console.error(`[notifications] Port ${NOTIFICATIONS_PORT} is already in use`);
		// Handle gracefully: retry with different port, notify user, etc.
	}
});
🤖 Prompt for AI Agents
In apps/desktop/src/shared/constants.ts around line 11, the notification port
constant is declared but the notification server initialization in
apps/desktop/src/main/windows/main.ts (line ~48) lacks EADDRINUSE handling;
update the server creation there to capture the returned server from
notificationsApp.listen and attach an "error" listener that checks err.code ===
"EADDRINUSE", logs a clear error mentioning NOTIFICATIONS_PORT, and implements a
graceful fallback (e.g., pick a different free port or surface a user-friendly
error/notification), following the same pattern used in
apps/desktop/src/main/lib/port-manager.ts.

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