Skip to content

fix(sentry): repair rc IPC handler registration, bridge socket path, and routing noise - #1341

Open
pedramamini wants to merge 1 commit into
rcfrom
fix/sentry-rc-duplicate-handler-socket-path
Open

fix(sentry): repair rc IPC handler registration, bridge socket path, and routing noise#1341
pedramamini wants to merge 1 commit into
rcfrom
fix/sentry-rc-duplicate-handler-socket-path

Conversation

@pedramamini

@pedramamini pedramamini commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Sentry triage on the rc channel. Four issues, all confirmed against current rc code and releases 0.18.4-RC / 0.18.5-RC.

MAESTRO-YA - duplicate IPC handler aborts startup registration (the important one)

Error: Attempted to register a second handler for 'director-notes:getRichOverviewStats' - 9 events, first seen ~19h after the back-merge landed, release 0.18.5-RC (= current package.json at HEAD).

070c2eee3 merge: back-merge origin/main into rc (2026-07-31) resolved db3c65946 by keeping both branches' copies of the rich-stats handler:

  • line 651 - rc's version: tallies AGENT-typed history entries (b6a052c39 gave cross-agent consults their own entry type)
  • line 778 - main's version: hardcodes agentEntryCount = 0 with a stale // Generate AI synopsis via batch-mode agent comment

ipcMain.handle throws on the second registration of a channel, and that throw escapes registerDirectorNotesHandlers into setupIpcHandlers() (no try/catch). Everything after it is skipped: director-notes:generateSynopsis, plus the ~40 register*Handlers calls that follow at index.ts:3338+ - cue, agents, process, persistence, system, claude, group chat, plugins, coworking, windows, stats, ssh-remote, filesystem, notifications and the rest.

Removed the main-side duplicate. The rc version is the correct one for this branch.

MAESTRO-WH - coworking bridge unusable when userData is deep

Error: listen EINVAL: invalid argument .../coworking.sock - 9 events, channel:rc.

getBridgeSocketPath() returned <userData>/coworking.sock unconditionally on POSIX, but sockaddr_un.sun_path is a fixed-size array: 104 bytes on macOS/BSD, 108 on Linux. The reported path is 123 bytes, so bind() rejected the address and coworking silently stayed unavailable for that install for good.

Over the limit, the socket now falls back to a short temp-dir path keyed by the same userData SHA-1 the Windows named pipe already uses (Windows solved this exact problem by hashing). The bridge and the agent-facing COWORKING_SOCKET_ENV_VAR both resolve through the one function, so they stay in sync automatically.

MAESTRO-YB / MAESTRO-YC - sshRemotes.find is not a function

Surfaced as failed git:status and git:numstat invokes, channel:rc, release 0.18.4-RC.

settings.json is a plain file in userData that users hand-edit and sync tools rewrite, and electron-store only substitutes the [] default when the key is absent - not when it holds a non-array. A malformed value took down every caller of the canonical getSshRemoteById(), including the Right Bar's git polls on agents that use no SSH remote at all.

Treat an unusable value as "no remotes configured" and logger.warn once so the bad file stays diagnosable rather than silently swallowed.

Note: five other sites read sshRemotes with their own inline copy of this lookup (marketplace.ts, agents.ts, autorun.ts, web-server-factory.ts, ssh-remote-resolver.ts). None of them has reported a crash, so they are left alone here - flagging for a future dedup pass.

MAESTRO-M4 - Group chat not found reported as a crash

Participants keep running after the user deletes their group chat, so the exit that fires later routes into a chat loadGroupChat can no longer find. The exit listener already recovers (participant marked done, buffer cleared) and retries once through the fallback path, so a single deleted chat produced two Sentry events per participant.

Follows the existing isExpectedGroomingFailure convention: a narrow predicate, applied only at the two exit-listener catches. Deliberately does not match Participant '...' not found (the chat still exists) or Group chat not found after participant update (that one means a broken write).

Tests

19 new assertions across 4 files. Every one confirmed failing against the unfixed source per the usual vacuity check (git stash push -- <source only>): 14 red before the fix, 54 green after.

The MAESTRO-YA test needed two angles - the existing suite's ipcMain mock is a Map, so a duplicate registration just overwrites the entry and goes unnoticed (which is why the bug shipped with tests passing). One test asserts on the raw call list, another re-runs registration against a mock that throws like the real ipcMain.

Validation

  • npx tsc --noEmit: 0 errors
  • Affected suites (coworking/, stores/, director-notes, git, groupChat, process-listeners, coworking-injection): 621 passed, 0 failed
  • ESLint: 7 pre-existing errors in examples/plugins/agent-flow/main.js, untouched by this branch

Investigated and deliberately skipped

  • MAESTRO-JG / S4 (MarketplaceFetchError: Network error fetching manifest: fetch failed) - already fixed, just unshipped. The sentryFilters.ts rule matches, and the LinkedErrors haystack fix that made it live is 6e5236a1c (2026-07-14). git merge-base --is-ancestor 6e5236a1c v0.18.4-RC = false, and all events are on 0.18.4-RC or stable 0.17.3. Re-fixing correct code was avoided.
  • MAESTRO-Y5 (sessions:setMany EBADF) - 1 event. EBADF on open can indicate a real fd leak, so adding it to the existing ENOSPC/EACCES/EPERM IPC-write rule could mask a genuine bug. Needs more signal first.
  • MAESTRO-Q0 (gh pr list failed) - per-issue breakdown is channel:stable, release 0.17.3. Not an rc issue.
  • MAESTRO-Q2 (1783 occ, maestro-p --status) - deferred a 6th time; still needs a human call on maestro-p exit semantics.
  • MAESTRO-RS (GLIBC_2.38 / better-sqlite3) - Linux build-infra, still needs a human.
  • Native/GPU crashes (SG, QG, TM, SZ, HQ, Y4, Y7, Y8, Y9, E1), Window unresponsive (62), renderer crashes (5A), minified vendor-react issues with no app frames (Y2/Y3 - and Y2 is the [Splash] twin of Y3), spawn EPERM/EINVAL/EFTYPE (NM, R0, X4 - genuine spawn signal).

Summary by CodeRabbit

  • Bug Fixes
    • Improved coworking connection reliability when user data paths exceed platform socket-length limits.
    • Prevented expected errors from deleted group chats from generating unnecessary error reports.
    • Hardened SSH remote lookup against malformed settings to avoid failures.
    • Removed duplicate Director’s Notes handler registration to improve IPC stability.
  • Tests
    • Expanded coverage across supported platforms and key error-handling scenarios.

…and routing noise

Four field issues from the rc channel (Sentry, release 0.18.4/0.18.5-RC).

MAESTRO-YA - "Attempted to register a second handler for
'director-notes:getRichOverviewStats'". The 2026-07-31 back-merge of main
into rc kept BOTH branches' copies of the rich-stats handler: rc's (which
tallies AGENT-typed history entries) at line 651 and main's (which hardcodes
that count to 0) at line 778. ipcMain.handle throws on the second
registration, and the throw escapes registerDirectorNotesHandlers into
setupIpcHandlers - so director-notes:generateSynopsis and every one of the
~40 register*Handlers calls after it (cue, agents, process, persistence,
stats, ssh-remote, filesystem, ...) never registered. Removed the main-side
duplicate; the rc version is the correct one for this branch.

MAESTRO-WH - "listen EINVAL" starting the coworking bridge. The POSIX socket
lived unconditionally at <userData>/coworking.sock, but sockaddr_un.sun_path
is a fixed 104 bytes on macOS (108 on Linux). A deep userData directory
produced a 123-byte path, bind() rejected it, and coworking silently stayed
unavailable for that install. Long paths now fall back to a short temp-dir
socket keyed by the same userData hash the Windows named pipe already uses.
Both the bridge and the agent-facing env var resolve through the one
function, so they stay in sync.

MAESTRO-YB / MAESTRO-YC - "sshRemotes.find is not a function" surfacing as
failed git:status and git:numstat invokes. electron-store only substitutes
the [] default when the key is absent, so a hand-edited or sync-mangled
settings.json hands getSshRemoteById a non-array and takes down callers that
use no SSH remote at all. Treat an unusable value as "no remotes configured"
and warn once so the bad file stays diagnosable.

MAESTRO-M4 - "Group chat not found: <id>" reported from the exit listener.
Participants keep running after the user deletes their group chat, so the
later exit routes into a chat that no longer loads. The listener already
recovers, and it retries once through the fallback path, so one deleted chat
produced two Sentry events per participant. Skip reporting for exactly that
message; every other routing failure still reports.

Regression tests for all four, each confirmed failing against the unfixed
source (14 red before, 54 green after).
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds platform-aware coworking socket paths, filters expected deleted-group-chat errors from Sentry, hardens SSH remote lookup, and removes duplicate Director’s Notes IPC registration.

Changes

Socket path resolution

Layer / File(s) Summary
Platform-aware socket paths
src/main/coworking/coworking-socket-path.ts, src/__tests__/main/coworking/coworking-socket-path.test.ts
POSIX paths use the user-data socket when within platform limits and a hashed temporary path otherwise. Windows named pipes use a shared user-data hash. Tests cover platform limits, uniqueness, and reload behavior.

Deleted group-chat error handling

Layer / File(s) Summary
Expected failure filtering
src/main/process-listeners/exit-listener.ts, src/__tests__/main/process-listeners/exit-listener-group-chat-noise.test.ts
isDeletedGroupChatFailure recognizes matching errors. Participant-exit paths skip Sentry capture for these failures while preserving capture for other errors.

SSH remote settings validation

Layer / File(s) Summary
Malformed SSH settings handling
src/main/stores/getters.ts, src/__tests__/main/stores/getters.test.ts
getSshRemoteById handles object, string, and null settings without throwing. It logs one warning per process for malformed values.

Director’s Notes IPC registration

Layer / File(s) Summary
Unique IPC handler registration
src/main/ipc/handlers/director-notes.ts, src/__tests__/main/ipc/handlers/directorNotes-richStats.test.ts
The duplicate Rich Overview stats registration is removed. Tests verify unique channels and successful registration of the synopsis handler.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fixes for IPC handler registration, bridge socket paths, and routing noise.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sentry-rc-duplicate-handler-socket-path

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

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

@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown

Greptile Summary

This PR repairs four rc-channel failures involving IPC registration, long coworking socket paths, malformed SSH settings, and expected group-chat exit noise.

  • Removes a duplicate Director's Notes IPC handler that aborted later startup registration.
  • Falls back to a short, stable temporary-directory coworking socket when the userData path exceeds Unix socket limits.
  • Adds defensive handling and diagnostics for malformed sshRemotes settings.
  • Suppresses Sentry reporting for participant exits after their group chat has been deleted.
  • Adds regression coverage for all four fixes.

Confidence Score: 4/5

The malformed SSH settings recovery should be completed before merging because an array containing null or primitive entries still crashes canonical lookup callers.

The primary fixes are well targeted and tested, but the new Array.isArray guard does not protect the following .find() callback from malformed array elements that can survive unvalidated settings deserialization.

Files Needing Attention: src/main/stores/getters.ts and src/tests/main/stores/getters.test.ts

Important Files Changed

Filename Overview
src/main/coworking/coworking-socket-path.ts Adds platform-aware Unix socket length enforcement and a stable hashed fallback under the temporary directory.
src/main/ipc/handlers/director-notes.ts Removes the duplicate rich overview statistics handler so registration can proceed to the remaining channels.
src/main/process-listeners/exit-listener.ts Narrowly suppresses Sentry capture for missing-chat errors during participant exit routing while preserving logging and cleanup.
src/main/stores/getters.ts Handles non-array SSH remote settings, but malformed members inside an array can still throw from the canonical lookup.
src/tests/main/coworking/coworking-socket-path.test.ts Covers platform limits, fallback length, per-userData uniqueness, and Windows named pipes.
src/tests/main/ipc/handlers/directorNotes-richStats.test.ts Detects duplicate channel registration with both call-list and Electron-faithful mocks.
src/tests/main/process-listeners/exit-listener-group-chat-noise.test.ts Verifies the missing-chat predicate matches only the intended message shape.
src/tests/main/stores/getters.test.ts Covers non-array malformed values but omits malformed elements inside an otherwise valid array.

Reviews (1): Last reviewed commit: "fix(sentry): repair rc IPC handler regis..." | Re-trigger Greptile

}
return undefined;
}
return sshRemotes.find((r) => r.id === sshRemoteId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Malformed remote entries still throw

If a hand-edited or sync-corrupted setting contains an array with null or primitive entries, Array.isArray passes but the callback dereferences r.id, causing git polling and other canonical getter callers to fail instead of treating the setting as unusable.

Suggested change
return sshRemotes.find((r) => r.id === sshRemoteId);
return sshRemotes.find(
(r) => r !== null && typeof r === 'object' && r.id === sshRemoteId
);

Context Used: CLAUDE.md (source)

Knowledge Base Used: Persistence: Settings, Sessions, and History

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/__tests__/main/process-listeners/exit-listener-group-chat-noise.test.ts (1)

18-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Ensure both Sentry filtering branches have integration coverage.

This file tests only isDeletedGroupChatFailure. It does not execute the participant-exit catch at src/main/process-listeners/exit-listener.ts Line [478] or the fallback catch at Line [502]. Add assertions that deleted-group-chat errors are not sent to Sentry and unrelated routing errors are still reported.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/main/process-listeners/exit-listener-group-chat-noise.test.ts`
around lines 18 - 52, Extend the tests beyond isDeletedGroupChatFailure to
exercise both Sentry filtering branches in the participant-exit and fallback
catch paths of exit-listener.ts. Assert that deleted-group-chat errors are
excluded from Sentry while unrelated routing failures are reported, covering
each branch through its integration-facing listener behavior.
src/__tests__/main/stores/getters.test.ts (1)

220-235: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the single-warning contract.

The production code promises one logger.warn call per process. This test checks only the return value. Add a focused case that calls getSshRemoteById twice with the same malformed value and asserts one warning. Isolate the test from the module-level warning state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/main/stores/getters.test.ts` around lines 220 - 235, Extend the
malformed sshRemotes tests around getSshRemoteById with a focused case that
invokes it twice using the same invalid stored value and verifies logger.warn is
called exactly once. Reset or otherwise isolate the module-level warning state
before the case so the assertion is independent of other tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/coworking/coworking-socket-path.ts`:
- Around line 45-51: Update the fallback path logic in the socket-path resolver
to validate the complete path against the POSIX socket path limit, including
os.tmpdir(). When it exceeds the limit, fall back to a known short POSIX
directory and shorter filename while preserving uniqueness; add a regression
test that mocks a long temporary directory and verifies the bounded result.

In `@src/main/stores/getters.ts`:
- Around line 135-144: Update the sshRemotes lookup in the getter containing the
malformed-array warning so its find callback validates each entry before reading
r.id, safely ignoring null or non-object entries while preserving valid remote
matching. Add a regression test covering an array containing a malformed entry
such as null and verify it does not throw.

---

Nitpick comments:
In `@src/__tests__/main/process-listeners/exit-listener-group-chat-noise.test.ts`:
- Around line 18-52: Extend the tests beyond isDeletedGroupChatFailure to
exercise both Sentry filtering branches in the participant-exit and fallback
catch paths of exit-listener.ts. Assert that deleted-group-chat errors are
excluded from Sentry while unrelated routing failures are reported, covering
each branch through its integration-facing listener behavior.

In `@src/__tests__/main/stores/getters.test.ts`:
- Around line 220-235: Extend the malformed sshRemotes tests around
getSshRemoteById with a focused case that invokes it twice using the same
invalid stored value and verifies logger.warn is called exactly once. Reset or
otherwise isolate the module-level warning state before the case so the
assertion is independent of other tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c63ffe35-47ab-49d7-a052-856052726ab8

📥 Commits

Reviewing files that changed from the base of the PR and between 37ff41b and d877db9.

📒 Files selected for processing (8)
  • src/__tests__/main/coworking/coworking-socket-path.test.ts
  • src/__tests__/main/ipc/handlers/directorNotes-richStats.test.ts
  • src/__tests__/main/process-listeners/exit-listener-group-chat-noise.test.ts
  • src/__tests__/main/stores/getters.test.ts
  • src/main/coworking/coworking-socket-path.ts
  • src/main/ipc/handlers/director-notes.ts
  • src/main/process-listeners/exit-listener.ts
  • src/main/stores/getters.ts
💤 Files with no reviewable changes (1)
  • src/main/ipc/handlers/director-notes.ts

Comment on lines +45 to +51
// The userData path is too deep to hold a bindable socket (long home
// directory, nested portable/test data dir, ...). Fall back to a short path
// under the temp dir, keyed by the same userData hash the Windows pipe uses
// so the socket stays unique per data directory. Callers all resolve the
// path through this function, so the bridge and the env var agents read
// (COWORKING_SOCKET_ENV_VAR) stay in sync automatically.
return path.join(os.tmpdir(), `maestro-coworking-${userDataSlug(userData)}.sock`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the fallback socket path before returning it.

Line 51 assumes that os.tmpdir() is short. os.tmpdir() can use a long environment-configured path. In that case, the fallback still exceeds SUN_PATH_MAX and the bridge cannot bind its socket.

Check the complete fallback path. If it is too long, use a known short POSIX directory and a shorter filename. Add a regression test with a mocked long temporary directory.

Proposed fix
-	return path.join(os.tmpdir(), `maestro-coworking-${userDataSlug(userData)}.sock`);
+	const slug = userDataSlug(userData);
+	const fallback = path.join(os.tmpdir(), `maestro-coworking-${slug}.sock`);
+	if (Buffer.byteLength(fallback) + 1 <= SUN_PATH_MAX) return fallback;
+
+	return path.join('/tmp', `m-${slug}.sock`);
📝 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
// The userData path is too deep to hold a bindable socket (long home
// directory, nested portable/test data dir, ...). Fall back to a short path
// under the temp dir, keyed by the same userData hash the Windows pipe uses
// so the socket stays unique per data directory. Callers all resolve the
// path through this function, so the bridge and the env var agents read
// (COWORKING_SOCKET_ENV_VAR) stay in sync automatically.
return path.join(os.tmpdir(), `maestro-coworking-${userDataSlug(userData)}.sock`);
// The userData path is too deep to hold a bindable socket (long home
// directory, nested portable/test data dir, ...). Fall back to a short path
// under the temp dir, keyed by the same userData hash the Windows pipe uses
// so the socket stays unique per data directory. Callers all resolve the
// path through this function, so the bridge and the env var agents read
// (COWORKING_SOCKET_ENV_VAR) stay in sync automatically.
const slug = userDataSlug(userData);
const fallback = path.join(os.tmpdir(), `maestro-coworking-${slug}.sock`);
if (Buffer.byteLength(fallback) + 1 <= SUN_PATH_MAX) return fallback;
return path.join('/tmp', `m-${slug}.sock`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/coworking/coworking-socket-path.ts` around lines 45 - 51, Update the
fallback path logic in the socket-path resolver to validate the complete path
against the POSIX socket path limit, including os.tmpdir(). When it exceeds the
limit, fall back to a known short POSIX directory and shorter filename while
preserving uniqueness; add a regression test that mocks a long temporary
directory and verifies the bounded result.

Comment on lines +135 to +144
if (!Array.isArray(sshRemotes)) {
if (!warnedAboutMalformedSshRemotes) {
warnedAboutMalformedSshRemotes = true;
logger.warn(
`Ignoring malformed 'sshRemotes' setting (expected an array, got ${typeof sshRemotes})`,
'Settings'
);
}
return undefined;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard malformed entries inside the array.

Array.isArray(sshRemotes) validates only the container. A hand-edited settings.json can contain [null], and the subsequent .find() callback still dereferences r.id. This causes another TypeError and leaves the Sentry failure unresolved. Guard each entry before reading id, and add a regression case for a malformed array entry.

Proposed guard
-	return sshRemotes.find((r) => r.id === sshRemoteId);
+	return sshRemotes.find((r) => r?.id === sshRemoteId);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/stores/getters.ts` around lines 135 - 144, Update the sshRemotes
lookup in the getter containing the malformed-array warning so its find callback
validates each entry before reading r.id, safely ignoring null or non-object
entries while preserving valid remote matching. Add a regression test covering
an array containing a malformed entry such as null and verify it does not throw.

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