Bramble: add gRPC remote execution mode with token auth#32
Conversation
Split Bramble into a server/client architecture so the TUI can connect to a remote server that manages sessions, worktrees, and task routing. Phase 1 - Extract interfaces: - SessionService, WorktreeService, TaskRouterService interfaces - Local implementations wrapping existing concrete types - TUI refactored to depend on interfaces instead of concrete types Phase 2-4 - gRPC infrastructure: - Proto definitions for all three services (bramble.proto) - EventBroadcaster for multi-client event fan-out - gRPC server implementations + proto<->Go converters - Client proxies (SessionProxy, WorktreeProxy, TaskRouterProxy) - `bramble serve` subcommand and `--remote` flag Security: - Shared-secret token auth with Bearer metadata on every RPC - Server generates random token on startup (or accepts --token/BRAMBLE_TOKEN) - Constant-time token comparison to prevent timing attacks - Default bind to localhost (not 0.0.0.0) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix data race on LocalWorktreeService.messages with sync.Mutex - Preserve operation messages in gRPC error responses via errWithMessages - Deduplicate SuggestBranchName (single source in service package) - Cache session output/info to avoid blocking RPCs in View() - Add SessionReconnectEvent for post-disconnect state resync - Warn when serving on non-localhost without TLS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| log.Printf("WARNING: session proxy events channel full, dropping reconnect event") | ||
| } | ||
| } | ||
| wasConnected = true |
There was a problem hiding this comment.
Spurious reconnect event on initial connection failure
Low Severity
In streamEvents, wasConnected is set to true when the initial StreamEvents call fails (line 56), even though no successful connection has ever been established. When a connection eventually succeeds, wasConnected is true, causing a SessionReconnectEvent to be emitted. This triggers a full state refresh via deferredRefreshCmd() on what is actually the first connection, not a reconnection.
| } | ||
|
|
||
| // Filter common words | ||
| filtered := make([]string, 0, len(words)) |
There was a problem hiding this comment.
Exported RouteTask function is unused in production code
Low Severity
RouteTask is an exported function that takes a concrete *taskrouter.Router parameter, but the TUI now routes tasks through routerSvc.Route() (via the TaskRouterService interface). The only caller of RouteTask is the integration test. This is dead production code that still imports taskrouter.Router directly, bypassing the new service abstraction.
| return status.Errorf(codes.Internal, format, err) | ||
| } | ||
| return status.Errorf(codes.Internal, format+"\nOutput:\n "+strings.Join(msgs, "\n "), err) | ||
| } |
There was a problem hiding this comment.
Format string injection in errWithMessages from message content
Medium Severity
errWithMessages concatenates user-controlled git output messages into the format string passed to status.Errorf. If any message from s.svc.Messages() contains % characters (e.g., filenames or git errors with %), status.Errorf (which uses fmt.Sprintf internally) interprets them as format verbs with no corresponding arguments, producing garbled %!X(MISSING) output in the error message shown to the user.


Summary
SessionService,WorktreeService,TaskRouterServiceinterfaces so local and remote implementations are interchangeablebramble serve --repo <name>subcommand andbramble --remote host:portclient mode--token/BRAMBLE_TOKEN)localhostwith exponential backoff on client reconnectTest plan
bazel test //bramble/...passes (unit + remote layer tests)scripts/lint.shpasses with 0 issuesbramble serve --repo <name>starts and prints auth tokenbramble --remote localhost:9090 --token <token>connects and shows TUI🤖 Generated with Claude Code
Note
High Risk
Introduces new networked client/server execution with streaming events and authentication interceptors, which is security- and correctness-sensitive. Bugs could impact session/worktree operations or expose remote control if token handling or transport assumptions are wrong (notably insecure-by-default connections).
Overview
Adds remote execution for Bramble via gRPC. The CLI now supports
bramble serveto run a headless server andbramble --remote host:portto run the TUI as a thin client using gRPC proxies.Refactors the TUI to depend on interfaces (
session.SessionService,service.WorktreeService,service.TaskRouterService) so local and remote implementations are swappable; worktree operations now return capturedMessages()instead of parsing command output in the UI, and task routing/branch-name heuristics move intoservice.Introduces a new
remotepackage with protobuf definitions/build rules, proto↔Go converters, an event broadcaster + streamingStreamEvents, reconnect signaling, and token-based auth interceptors/Per-RPC credentials; additionally, session rendering is changed to use cached session info/output to avoid blocking remote calls in the render path.Written by Cursor Bugbot for commit 028bb0e. This will update automatically on new commits. Configure here.