Skip to content

fix: BrainBar socket stall — serial queue deadlock blocks all new MCP connections#87

Merged
EtanHey merged 2 commits intomainfrom
fix/brainbar-socket-stall
Mar 18, 2026
Merged

fix: BrainBar socket stall — serial queue deadlock blocks all new MCP connections#87
EtanHey merged 2 commits intomainfrom
fix/brainbar-socket-stall

Conversation

@EtanHey
Copy link
Copy Markdown
Owner

@EtanHey EtanHey commented Mar 18, 2026

Summary

  • P0 fix: sendResponse() had an infinite usleep(1000) retry loop when write() returned EAGAIN — one slow socat client pinned the serial DispatchQueue, blocking acceptClient() for all new connections
  • Reordered startup: socket binds BEFORE database init (eliminates race condition where Claude Code connects before socket exists)
  • Added raw JSON-RPC fallback in framing parser (prevents silent hangs when Content-Length header is missing)
  • resources/list, prompts/list, ping now return proper empty results instead of -32601 errors
  • DB open failure now logs prominently instead of silently starting a broken daemon

Test plan

  • Standard Content-Length framing: initialize → tools/list → tools/call all respond
  • Raw JSON without framing: now responds (previously hung forever)
  • Zombie client test: slow client doesn't block new connections
  • Kill → restart → immediate connect: socket ready within 500ms
  • Full MCP lifecycle: initialize, notifications/initialized, tools/list, resources/list, prompts/list, ping, brain_search — all pass
  • swift build -c release clean

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Added resources, prompts, and ping endpoints with consistent response formatting.
  • Bug Fixes

    • Improved connection stability with retransmission/backoff and clearer disconnect handling.
    • Implemented a raw JSON-RPC fallback (newline-delimited + tail handling) for more robust message parsing.
    • More robust handling of notifications and id binding to ensure correct responses.

…ilent failures

P0 fix: sendResponse() had an infinite usleep retry loop when write()
returned EAGAIN. One slow/hung socat client pinned the serial
DispatchQueue, blocking acceptClient() for ALL new connections.

Also fixes:
- Startup race: socket now binds BEFORE database init so connections
  are accepted immediately after launch
- Silent DB failure: logs prominently when database fails to open
- Raw JSON fallback: framing parser now handles bare JSON-RPC without
  Content-Length headers (prevents silent hangs on manual tests)
- NSNull notifications: handles id:null as a proper notification
- Missing MCP methods: resources/list, prompts/list, ping now return
  proper empty results instead of -32601 errors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@EtanHey
Copy link
Copy Markdown
Owner Author

EtanHey commented Mar 18, 2026

@coderabbitai review

@EtanHey
Copy link
Copy Markdown
Owner Author

EtanHey commented Mar 18, 2026

@cursor @BugBot review

@EtanHey
Copy link
Copy Markdown
Owner Author

EtanHey commented Mar 18, 2026

@codex review

@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 18, 2026

You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 18, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 50d3e919-2b5f-42e7-a470-b2fe15fb955f

📥 Commits

Reviewing files that changed from the base of the PR and between a5a58bf and 96a70e8.

📒 Files selected for processing (1)
  • brain-bar/Sources/BrainBar/MCPRouter.swift
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 87
File: brain-bar/Sources/BrainBar/BrainBarServer.swift:118-129
Timestamp: 2026-03-18T00:12:08.774Z
Learning: In `brain-bar/Sources/BrainBar/MCPRouter.swift` (Swift, BrainBar daemon), the socket-before-DB startup pattern means the Unix socket binds immediately (~1ms) while the database may take several seconds to open on cold start (8GB file). Any tool handler that accesses `database` MUST throw an explicit error (e.g., `ToolError.noDatabase`) when `database` is nil — never return empty or default results (e.g., `guard let db else { return "[]" }` is forbidden). The false-success pattern hides startup timing issues from MCP clients. Flag any `guard let db = database else { return ... }` patterns that silently return defaults instead of throwing.
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 87
File: brain-bar/Sources/BrainBar/MCPRouter.swift:0-0
Timestamp: 2026-03-18T00:12:29.551Z
Learning: In `brain-bar/Sources/BrainBar/MCPRouter.swift` (Swift, BrainBar MCP daemon), the notification guard `let isNotification = (rawID == nil || rawID is NSNull)` is the single and only point where a no-response decision is made. Any message that passes this guard has a non-nil, non-NSNull id and MUST return a proper JSON-RPC response. Returning `[:]` (empty dict = no response) anywhere after the notification guard is always a bug — it creates a silent client hang. Flag any `return [:]` that appears after the guard in future reviews.
Learnt from: CR
Repo: EtanHey/brainlayer PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-14T02:20:54.656Z
Learning: Request codex review, cursor review, and bugbot review for BrainLayer PRs
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 0
File: :0-0
Timestamp: 2026-03-17T01:04:11.749Z
Learning: The socket path `/tmp/brainbar.sock` is intentional for the BrainBar Swift daemon (brain-bar/) and must NOT be changed to `/tmp/brainlayer.sock`. BrainBar is a new daemon that coexists with the existing Python `brainlayer-mcp` (which uses `/tmp/brainlayer.sock`) during the migration period. The different paths avoid conflicts and allow A/B testing. Once BrainBar is proven stable, the Python server will be retired and `.mcp.json` will point to `/tmp/brainbar.sock` via socat.
Learnt from: CR
Repo: EtanHey/brainlayer PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-14T02:20:54.656Z
Learning: Treat retrieval correctness, write safety, and MCP stability as critical-path concerns in BrainLayer reviews
📚 Learning: 2026-03-18T00:12:29.551Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 87
File: brain-bar/Sources/BrainBar/MCPRouter.swift:0-0
Timestamp: 2026-03-18T00:12:29.551Z
Learning: In `brain-bar/Sources/BrainBar/MCPRouter.swift` (Swift, BrainBar MCP daemon), the notification guard `let isNotification = (rawID == nil || rawID is NSNull)` is the single and only point where a no-response decision is made. Any message that passes this guard has a non-nil, non-NSNull id and MUST return a proper JSON-RPC response. Returning `[:]` (empty dict = no response) anywhere after the notification guard is always a bug — it creates a silent client hang. Flag any `return [:]` that appears after the guard in future reviews.

Applied to files:

  • brain-bar/Sources/BrainBar/MCPRouter.swift
📚 Learning: 2026-03-18T00:12:08.774Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 87
File: brain-bar/Sources/BrainBar/BrainBarServer.swift:118-129
Timestamp: 2026-03-18T00:12:08.774Z
Learning: In Swift files under brain-bar/Sources/BrainBar, enforce that when a critical dependency like the database is nil due to startup ordering (socket before DB), any tool handler that accesses the database must throw an explicit error (e.g., ToolError.noDatabase) instead of returning a default/empty value. Do not allow silent defaults (e.g., guard let db else { return ... }). Flag patterns that silently return defaults when db is nil, as this masks startup timing issues. This guidance applies broadly to similar Swift files in the BrainBar module, not just this one location.

Applied to files:

  • brain-bar/Sources/BrainBar/MCPRouter.swift
📚 Learning: 2026-03-17T01:04:22.497Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 0
File: :0-0
Timestamp: 2026-03-17T01:04:22.497Z
Learning: Applies to src/brainlayer/mcp/**/*.py and brain-bar/Sources/BrainBar/MCPRouter.swift: The 8 required MCP tools are `brain_search`, `brain_store`, `brain_recall`, `brain_entity`, `brain_expand`, `brain_update`, `brain_digest`, `brain_tags`. `brain_tags` is the 8th tool, replacing `brain_get_person`, as defined in the Phase B spec merged in PR `#72`. The Python MCP server already implements `brain_tags`. Legacy `brainlayer_*` aliases must be maintained for backward compatibility.

Applied to files:

  • brain-bar/Sources/BrainBar/MCPRouter.swift
🔇 Additional comments (5)
brain-bar/Sources/BrainBar/MCPRouter.swift (5)

27-34: LGTM — notification detection is robust.

The dual check for both missing key (nil) and explicit JSON null (NSNull) correctly handles all notification cases per JSON-RPC spec. The force unwrap on line 34 is safe since isNotification == false guarantees rawID is neither nil nor NSNull.


39-41: Previous review issue resolved.

The notifications/initialized handler now correctly returns a proper JSON-RPC response with the id, preventing client hangs when this notification is sent as a request.


46-51: LGTM — new RPC methods return proper empty results.

resources/list, prompts/list, and ping now return valid JSON-RPC responses instead of -32601 errors, aligning with MCP protocol expectations for servers without resources or prompts.


158-159: Correct: throws explicit error when database unavailable.

Throwing ToolError.noDatabase instead of returning a silent empty result correctly surfaces the startup timing issue to MCP clients. This aligns with the socket-before-DB startup pattern. Based on learnings: "Any tool handler that accesses database MUST throw an explicit error (e.g., ToolError.noDatabase) when database is nil — never return empty or default results."


226-232: LGTM — clean helper for consistent response formatting.

The jsonRPCResult helper ensures a consistent JSON-RPC 2.0 response structure for simple result payloads, reducing duplication across the new method handlers.


📝 Walkthrough

Walkthrough

Router and socket setup have been moved ahead of database initialization; database wiring is deferred until after the server listens. Socket write logic gains an EAGAIN retry/backoff with a max cap and centralized client disconnect handling. Framing gains a raw JSON-RPC fallback and router adds new RPC methods and notification handling.

Changes

Cohort / File(s) Summary
Server init & connection handling
brain-bar/Sources/BrainBar/BrainBarServer.swift
Router created before DB binding; socket bound before DB init; DB initialization deferred until after listening. Added per-connection EAGAIN/EWOULDBLOCK retry counter (capped at 50), retransmit/backoff logic, error logging, and private func disconnectClient(fd:).
Message framing fallback
brain-bar/Sources/BrainBar/MCPFraming.swift
Added two-mode extractMessages: retains Content-Length framing and introduces a raw JSON-RPC fallback that parses newline-delimited JSON and advances the buffer when no Content-Length header is present.
Router / RPC handling
brain-bar/Sources/BrainBar/MCPRouter.swift
Treats missing or null id as notifications; extracts raw ID for responses; adds jsonRPCResult helper; supports resources/list, prompts/list, and ping; no-op handling for notifications/initialized; throws ToolError.noDatabase when DB absent in error paths.

Sequence Diagram

sequenceDiagram
    participant Start as Server Start
    participant Router as Router
    participant Socket as Socket
    participant DB as Database
    participant Client as Client

    rect rgba(100, 200, 150, 0.5)
    Note over Start,Router: Initialization sequence
    Start->>Router: Initialize (no DB dependency)
    Router-->>Start: Ready
    Start->>Socket: Create & bind socket
    Socket-->>Start: Listening
    Start->>DB: Initialize asynchronously
    DB-->>Start: Opened or failed (logged)
    alt DB opened
        DB->>Router: Wire DB to router
    end
    end

    rect rgba(150, 150, 200, 0.5)
    Note over Client,Socket: Write with EAGAIN backoff
    Client->>Socket: Send response
    alt Write success (partial/full)
        Socket-->>Client: Bytes written → reset retries
    else EAGAIN / EWOULDBLOCK
        Client->>Client: Increment eagainRetries
        alt eagainRetries < 50
            Client->>Socket: Retry write after short backoff
        else eagainRetries >= 50
            Client->>Client: Drop client, log and disconnect
        end
    else Other write error
        Client->>Client: Log error and disconnectClient(fd:)
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Router hopped first, then sockets unfurled,
DB waited patient while listeners whirled—
Retries counted softly, fifty small hops,
Framing fell back when headers would stop,
New RPC doors opened; the rabbit applauds. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing a socket stall caused by serial queue deadlock that blocks MCP connections, which aligns with the P0 fix removing the infinite usleep retry loop and reordering startup.

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

✨ 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/brainbar-socket-stall
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
brain-bar/Sources/BrainBar/BrainBarServer.swift (1)

181-214: ⚠️ Potential issue | 🟠 Major

Disconnect during send can be undone by stale state writeback.

sendResponse now calls disconnectClient(fd:) on stall/error, but readFromClient later unconditionally writes clients[fd] = state (Line 178). That can reinsert a disconnected client entry and leave stale tracking.

Suggested fix
-        clients[fd] = state
+        if var current = clients[fd] {
+            current.framing = state.framing
+            clients[fd] = current
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@brain-bar/Sources/BrainBar/BrainBarServer.swift` around lines 181 - 214,
sendResponse calls disconnectClient(fd:) on errors but readFromClient still
unconditionally does clients[fd] = state, which can reinsert a disconnected
client and revive stale state; fix by making readFromClient check that the
client is still present and not cancelled before writing back (e.g., verify
clients[fd] exists and clients[fd]?.source.isCancelled == false or guard let
client = clients[fd] else { return }), or update disconnectClient to mark a
removed/closed flag that readFromClient checks before assigning, ensuring
clients[fd] is only updated if the client remains connected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@brain-bar/Sources/BrainBar/BrainBarServer.swift`:
- Around line 118-129: The startup now accepts connections before the DB is
ready but MCPRouter's brain_search handler returns a false-success `"[]"` when
database == nil; change the handler in MCPRouter (the brain_search route
implementation around where it checks for a nil database) to detect a nil or
not-ready database and return an explicit readiness error/HTTP error message
(matching the contract used elsewhere, e.g., the graceful error path referenced
in BrainBarServer.swift and other tools/call behavior) instead of returning an
empty array string; ensure you reference the router.setDatabase/database
property semantics so once BrainBarServer assigns database the brain_search
handler resumes normal behavior.

In `@brain-bar/Sources/BrainBar/MCPRouter.swift`:
- Around line 39-41: The handler for the "notifications/initialized" branch
currently returns an empty dictionary even when an RPC id is present, which
leaves request-mode clients waiting; update the "notifications/initialized" case
in MCPRouter.swift so that when an id has been validated as non-notification it
produces a proper RPC response containing that id and a success result (e.g., an
empty result body or explicit status) instead of returning [:], ensuring the
response shape matches other RPC responses emitted by the router.

---

Outside diff comments:
In `@brain-bar/Sources/BrainBar/BrainBarServer.swift`:
- Around line 181-214: sendResponse calls disconnectClient(fd:) on errors but
readFromClient still unconditionally does clients[fd] = state, which can
reinsert a disconnected client and revive stale state; fix by making
readFromClient check that the client is still present and not cancelled before
writing back (e.g., verify clients[fd] exists and
clients[fd]?.source.isCancelled == false or guard let client = clients[fd] else
{ return }), or update disconnectClient to mark a removed/closed flag that
readFromClient checks before assigning, ensuring clients[fd] is only updated if
the client remains connected.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0b449472-969f-4b0b-9b80-e87f739efc9b

📥 Commits

Reviewing files that changed from the base of the PR and between 461c59f and a5a58bf.

📒 Files selected for processing (3)
  • brain-bar/Sources/BrainBar/BrainBarServer.swift
  • brain-bar/Sources/BrainBar/MCPFraming.swift
  • brain-bar/Sources/BrainBar/MCPRouter.swift
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 0
File: :0-0
Timestamp: 2026-03-17T01:04:11.749Z
Learning: The socket path `/tmp/brainbar.sock` is intentional for the BrainBar Swift daemon (brain-bar/) and must NOT be changed to `/tmp/brainlayer.sock`. BrainBar is a new daemon that coexists with the existing Python `brainlayer-mcp` (which uses `/tmp/brainlayer.sock`) during the migration period. The different paths avoid conflicts and allow A/B testing. Once BrainBar is proven stable, the Python server will be retired and `.mcp.json` will point to `/tmp/brainbar.sock` via socat.
📚 Learning: 2026-03-17T01:04:22.497Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 0
File: :0-0
Timestamp: 2026-03-17T01:04:22.497Z
Learning: In BrainLayer, the BrainBar daemon uses the socket path `/tmp/brainbar.sock` (NOT `/tmp/brainlayer.sock`). BrainBar is a new native Swift daemon designed to coexist with the existing Python `brainlayer-mcp` server during the migration period. Different socket paths avoid conflicts and enable A/B testing. Once BrainBar is proven stable, the Python server will be retired.

Applied to files:

  • brain-bar/Sources/BrainBar/BrainBarServer.swift
📚 Learning: 2026-03-17T01:04:11.749Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 0
File: :0-0
Timestamp: 2026-03-17T01:04:11.749Z
Learning: The socket path `/tmp/brainbar.sock` is intentional for the BrainBar Swift daemon (brain-bar/) and must NOT be changed to `/tmp/brainlayer.sock`. BrainBar is a new daemon that coexists with the existing Python `brainlayer-mcp` (which uses `/tmp/brainlayer.sock`) during the migration period. The different paths avoid conflicts and allow A/B testing. Once BrainBar is proven stable, the Python server will be retired and `.mcp.json` will point to `/tmp/brainbar.sock` via socat.

Applied to files:

  • brain-bar/Sources/BrainBar/BrainBarServer.swift
🔇 Additional comments (3)
brain-bar/Sources/BrainBar/MCPFraming.swift (1)

74-102: Raw JSON fallback is a good resilience addition.

The newline-first + tail parse strategy is pragmatic and keeps incomplete trailing data buffered correctly.

brain-bar/Sources/BrainBar/MCPRouter.swift (1)

46-51: New MCP methods and shared result helper look correct.

Returning protocol-valid empty payloads for resources/list, prompts/list, and ping is the right behavior.

Also applies to: 226-232

brain-bar/Sources/BrainBar/BrainBarServer.swift (1)

185-197: Good fix: bounded EAGAIN retries eliminate queue pinning.

Capping retries and dropping stalled clients addresses the prior infinite-loop deadlock class.

Comment thread brain-bar/Sources/BrainBar/BrainBarServer.swift
Comment thread brain-bar/Sources/BrainBar/MCPRouter.swift Outdated
…ation ack

- brain_search now throws noDatabase error instead of returning "[]"
  when DB hasn't loaded yet (reachable during socket-before-DB startup)
- notifications/initialized with an id now returns a proper ack
  instead of empty response (which would hang the client)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@EtanHey EtanHey merged commit 9e6ac8b into main Mar 18, 2026
6 checks passed
@EtanHey EtanHey deleted the fix/brainbar-socket-stall branch March 18, 2026 00:16
EtanHey added a commit that referenced this pull request Mar 18, 2026
After sendResponse() calls disconnectClient(fd:) to drop a stalled
client, readFromClient() would write the ClientState copy back into
the clients dictionary at line 178, re-inserting a zombie entry with
a cancelled DispatchSource. Now checks clients[fd] after each
sendResponse() and bails if the client was removed.

Also: write() returning 0 now properly disconnects instead of
silently breaking the write loop.

Found by code review agent on PR #87.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EtanHey added a commit that referenced this pull request Mar 18, 2026
After sendResponse() calls disconnectClient(fd:) to drop a stalled
client, readFromClient() would write the ClientState copy back into
the clients dictionary at line 178, re-inserting a zombie entry with
a cancelled DispatchSource. Now checks clients[fd] after each
sendResponse() and bails if the client was removed.

Also: write() returning 0 now properly disconnects instead of
silently breaking the write loop.

Found by code review agent on PR #87.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EtanHey added a commit that referenced this pull request Mar 29, 2026
…gle-instance

Quick Capture controller with capture→store and search→results flows:
- QuickCaptureController: capture (brain_store) and search (brain_search)
  with formatted output. Empty content validation. Default importance 5.
- QuickCapturePanelState: mode (capture/search), visibility, toggle/dismiss
- HotkeyManager + GestureStateMachine: CGEventTap for F4 hotkey (keycodes
  118+129), hold/tap/double-tap detection. Ported from VoiceBar PR #87.

Single-instance enforcement:
- BrainBarApp checks NSRunningApplication on launch, exits if another
  instance is already running. Prevents duplicate menu bar icons.
- build-app.sh kills existing BrainBar processes before installing.

11 new tests: capture flow (3), search flow (2), panel state (2),
hotkey config (1), gesture state machine (3).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EtanHey added a commit that referenced this pull request Mar 29, 2026
…gle-instance

Quick Capture controller with capture→store and search→results flows:
- QuickCaptureController: capture (brain_store) and search (brain_search)
  with formatted output. Empty content validation. Default importance 5.
- QuickCapturePanelState: mode (capture/search), visibility, toggle/dismiss
- HotkeyManager + GestureStateMachine: CGEventTap for F4 hotkey (keycodes
  118+129), hold/tap/double-tap detection. Ported from VoiceBar PR #87.

Single-instance enforcement:
- BrainBarApp checks NSRunningApplication on launch, exits if another
  instance is already running. Prevents duplicate menu bar icons.
- build-app.sh kills existing BrainBar processes before installing.

11 new tests: capture flow (3), search flow (2), panel state (2),
hotkey config (1), gesture state machine (3).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EtanHey added a commit that referenced this pull request Mar 29, 2026
* feat: add BrainBar quick capture foundation — controller, hotkey, single-instance

Quick Capture controller with capture→store and search→results flows:
- QuickCaptureController: capture (brain_store) and search (brain_search)
  with formatted output. Empty content validation. Default importance 5.
- QuickCapturePanelState: mode (capture/search), visibility, toggle/dismiss
- HotkeyManager + GestureStateMachine: CGEventTap for F4 hotkey (keycodes
  118+129), hold/tap/double-tap detection. Ported from VoiceBar PR #87.

Single-instance enforcement:
- BrainBarApp checks NSRunningApplication on launch, exits if another
  instance is already running. Prevents duplicate menu bar icons.
- build-app.sh kills existing BrainBar processes before installing.

11 new tests: capture flow (3), search flow (2), panel state (2),
hotkey config (1), gesture state machine (3).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: @BugBot comprehensive review - 8 bugs identified (2 critical, 3 high, 3 medium)

Co-authored-by: Etan Heyman <EtanHey@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Etan Heyman <EtanHey@users.noreply.github.com>
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