Skip to content

Bramble: add gRPC remote execution mode with token auth#32

Open
mzhaom wants to merge 2 commits into
mainfrom
feature/remote-exec
Open

Bramble: add gRPC remote execution mode with token auth#32
mzhaom wants to merge 2 commits into
mainfrom
feature/remote-exec

Conversation

@mzhaom

@mzhaom mzhaom commented Feb 11, 2026

Copy link
Copy Markdown
Member

Summary

  • Split Bramble into server/client architecture with gRPC so the TUI can connect to a remote server managing sessions, worktrees, and task routing
  • Extract SessionService, WorktreeService, TaskRouterService interfaces so local and remote implementations are interchangeable
  • Add bramble serve --repo <name> subcommand and bramble --remote host:port client mode
  • Implement shared-secret token authentication with Bearer metadata on every RPC (auto-generated or via --token/BRAMBLE_TOKEN)
  • Default server bind to localhost with exponential backoff on client reconnect

Test plan

  • bazel test //bramble/... passes (unit + remote layer tests)
  • scripts/lint.sh passes with 0 issues
  • Converter roundtrip tests verify all proto<->Go type mappings
  • gRPC server E2E tests verify all RPCs through real server+proxy
  • Auth tests verify token enforcement on unary and streaming RPCs
  • Verify bramble serve --repo <name> starts and prints auth token
  • Verify bramble --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 serve to run a headless server and bramble --remote host:port to 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 captured Messages() instead of parsing command output in the UI, and task routing/branch-name heuristics move into service.

Introduces a new remote package with protobuf definitions/build rules, proto↔Go converters, an event broadcaster + streaming StreamEvents, 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.

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>
Comment thread bramble/service/worktree.go
Comment thread bramble/remote/server.go
Comment thread bramble/service/taskrouter.go
- 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>

@cursor cursor 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.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Comment thread bramble/app/taskmodal.go
}

// Filter common words
filtered := make([]string, 0, len(words))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Comment thread bramble/remote/server.go
return status.Errorf(codes.Internal, format, err)
}
return status.Errorf(codes.Internal, format+"\nOutput:\n "+strings.Join(msgs, "\n "), err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

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