feat(bridge): daemon polls a registry of repos, not one env var - #422
Conversation
The GitHub bridge daemon has no reliable cwd (systemd/launchd set none), so it previously watched exactly one repo via AGENTFLARE_BRIDGE_REPO. A workstation with more than one agentflare-linked repo could only bridge one of them at a time. Add a bridge_repos table (the reverse index of .agentflare/project.json, keyed by repo instead of folder) that resolve_project() keeps current on every CLI/MCP call made from inside a linked repo. The daemon reads it fresh each tick and polls every registered repo, sharing one GitHub credential across them. AGENTFLARE_BRIDGE_REPO still works as a manual one-off override, merged in and deduped against the registry. This also fixes a latent bug where build_ctx resolved project_id via resolve_project()'s cwd-based git-remote lookup, independent of the repo actually being polled -- under a real systemd/launchd daemon (no cwd) this could silently link the wrong project. Sourcing project_id from the registry instead removes that cwd dependency. Agentflare-Agent: claude-code Agentflare-Branch: bridge-multi-repo-registry
📝 WalkthroughWalkthroughThe change adds persistent GitHub bridge repository records, registers repositories during project resolution, and changes the bridge runner from single-repository polling to per-tick multi-repository polling. ChangesGitHub bridge registry
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant BridgeRunner
participant bridge_targets
participant bridge_repo
participant ClaimLedger
BridgeRunner->>bridge_targets: discover repositories on each tick
bridge_targets->>bridge_repo: list registered repositories
bridge_targets-->>BridgeRunner: return repository targets
BridgeRunner->>ClaimLedger: open repository claim ledger
ClaimLedger-->>BridgeRunner: return ledger or failure
BridgeRunner->>BridgeRunner: poll each repository independently
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/github/bridge/runner.rs`:
- Around line 246-251: Update the test setup around PATH_LOCK and the removal of
AGENTFLARE_BRIDGE_REPO to capture its original value with std::env::var_os
before removing it, then create a scope guard that restores the variable to its
prior value—or removes it if it was absent—before the guard releases PATH_LOCK.
- Around line 61-71: Validate the AGENTFLARE_BRIDGE_REPO override against the
current directory before constructing the BridgeTarget: resolve the current
repository’s GitHub identity, compare it with repo, and reject or skip the
target when they differ. Update the runner flow around
AgentflareMcp::resolve_project and preserve project resolution only for matching
repositories, preventing run_once from pairing one repository with another
repository’s project.
In `@src/mcp_server.rs`:
- Around line 956-963: Update the upsert call in resolve_project to resolve the
repository-specific work-agent setting instead of always passing None. Preserve
an existing bridge_repos.work_agent when no repository setting is configured,
either by supplying the resolved value or adjusting the upsert policy to avoid
overwriting it with NULL.
🪄 Autofix
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
Run ID: aea8dd19-17a6-4423-9534-b0ed8c137e06
📒 Files selected for processing (7)
crates/agentflare-backend/src/bridge_repo.rscrates/agentflare-backend/src/db.rscrates/agentflare-backend/src/lib.rscrates/agentflare-backend/src/migrations/0008_bridge_repos.sqlsrc/github/bridge/runner.rssrc/github/client.rssrc/mcp_server.rs
| let repo_root = std::env::current_dir().unwrap_or_default(); | ||
| let mcp = crate::mcp_server::AgentflareMcp::default(); | ||
| match mcp.resolve_project(conn) { | ||
| Ok(p) => targets.push(BridgeTarget { | ||
| repo, | ||
| project_id: p.id, | ||
| queue_label: crate::github::bridge::config::resolve_project_queue_label( | ||
| &repo_root, | ||
| ), | ||
| work_agent: None, | ||
| }), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Validate the override against the current repository.
If AGENTFLARE_BRIDGE_REPO names repository A and the current directory is repository B, resolve_project() returns B's project while this code creates a target for A. run_once() can then create or update items for A in B's project.
Resolve the current directory's GitHub repository before resolve_project(). Reject the override when it does not match repo. Alternatively, load the explicit repository's existing registry row.
Suggested guard
let repo_root = std::env::current_dir().unwrap_or_default();
+let Some(cwd_repo) = RepoId::resolve_from_remote(&repo_root) else {
+ eprintln!("github bridge: override requires a GitHub repository cwd; ignoring");
+ return targets;
+};
+if cwd_repo != repo {
+ eprintln!("github bridge: override does not match the current repository; ignoring");
+ return targets;
+}
let mcp = crate::mcp_server::AgentflareMcp::default();📝 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.
| let repo_root = std::env::current_dir().unwrap_or_default(); | |
| let mcp = crate::mcp_server::AgentflareMcp::default(); | |
| match mcp.resolve_project(conn) { | |
| Ok(p) => targets.push(BridgeTarget { | |
| repo, | |
| project_id: p.id, | |
| queue_label: crate::github::bridge::config::resolve_project_queue_label( | |
| &repo_root, | |
| ), | |
| work_agent: None, | |
| }), | |
| let repo_root = std::env::current_dir().unwrap_or_default(); | |
| let Some(cwd_repo) = RepoId::resolve_from_remote(&repo_root) else { | |
| eprintln!("github bridge: override requires a GitHub repository cwd; ignoring"); | |
| return targets; | |
| }; | |
| if cwd_repo != repo { | |
| eprintln!("github bridge: override does not match the current repository; ignoring"); | |
| return targets; | |
| } | |
| let mcp = crate::mcp_server::AgentflareMcp::default(); | |
| match mcp.resolve_project(conn) { | |
| Ok(p) => targets.push(BridgeTarget { | |
| repo, | |
| project_id: p.id, | |
| queue_label: crate::github::bridge::config::resolve_project_queue_label( | |
| &repo_root, | |
| ), | |
| work_agent: None, | |
| }), |
🤖 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/github/bridge/runner.rs` around lines 61 - 71, Validate the
AGENTFLARE_BRIDGE_REPO override against the current directory before
constructing the BridgeTarget: resolve the current repository’s GitHub identity,
compare it with repo, and reject or skip the target when they differ. Update the
runner flow around AgentflareMcp::resolve_project and preserve project
resolution only for matching repositories, preventing run_once from pairing one
repository with another repository’s project.
| let _guard = agent_registry::detect::PATH_LOCK | ||
| .lock() | ||
| .unwrap_or_else(|e| e.into_inner()); | ||
| unsafe { | ||
| std::env::remove_var("AGENTFLARE_BRIDGE_REPO"); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Restore AGENTFLARE_BRIDGE_REPO after the test.
Line 250 changes a process-global environment variable and does not restore its inherited value. A later test can observe the changed environment.
Save std::env::var_os("AGENTFLARE_BRIDGE_REPO") before removal. Restore it with a scope guard before releasing the environment lock.
🤖 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/github/bridge/runner.rs` around lines 246 - 251, Update the test setup
around PATH_LOCK and the removal of AGENTFLARE_BRIDGE_REPO to capture its
original value with std::env::var_os before removing it, then create a scope
guard that restores the variable to its prior value—or removes it if it was
absent—before the guard releases PATH_LOCK.
| let _ = agentflare_backend::bridge_repo::upsert( | ||
| conn, | ||
| &repo_id.to_string(), | ||
| project_id, | ||
| &folder_path.to_string_lossy(), | ||
| &queue_label, | ||
| None, | ||
| crate::claims::now(), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Preserve the repository-specific work agent.
Line 962 always writes None. Each resolve_project() call therefore clears a previously stored bridge_repos.work_agent. The bridge then falls back to the daemon-wide agent instead of the repository-specific agent.
Resolve and pass the repository work-agent setting here. If no such setting exists, change the upsert policy so registration does not overwrite an existing value with NULL.
🤖 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/mcp_server.rs` around lines 956 - 963, Update the upsert call in
resolve_project to resolve the repository-specific work-agent setting instead of
always passing None. Preserve an existing bridge_repos.work_agent when no
repository setting is configured, either by supplying the resolved value or
adjusting the upsert policy to avoid overwriting it with NULL.
Agentflare-Agent: claude-code Agentflare-Branch: bridge-multi-repo-registry
Agentflare-Agent: claude-code Agentflare-Branch: bridge-multi-repo-registry
Summary
bridge_repostable (migration 0008) — the reverse index of.agentflare/project.json, keyed by repo instead of folder — thatresolve_project()keeps current on every CLI/MCP call made from inside a linked repo.github/bridge/runner.rs) now reads that registry fresh each tick and polls every registered repo instead of requiring a singleAGENTFLARE_BRIDGE_REPO.Clientis nowCloneso one GitHub credential is shared across repos in a tick.AGENTFLARE_BRIDGE_REPOstill works as a manual one-off override, merged in and deduped against the registry — no regression for existing single-repo/dev setups.build_ctxused to resolveproject_idviaresolve_project()'s cwd-based git-remote lookup, independent of the repo actually being polled — under a real systemd/launchd daemon (no cwd) this could silently link the wrong project.project_idnow comes from the registry instead.Background: found while debugging why a cross-workstation issue (#421, labeled
agentflare) sat unclaimed — this workstation's daemon had noAGENTFLARE_BRIDGE_*env vars at all, so its bridge loop was a no-op. That's a separate, already-fixed local config issue; this PR is the architectural follow-up so a workstation with multiple linked repos doesn't hit the same "only one repo bridged" ceiling.Test plan
cargo test -p agentflare-backend— 96 passedcargo test --bin agentflare github::bridge— 104 passed (incl. newbridge_targets_reads_every_registered_repo)cargo test --bin agentflare mcp_server::— 185 passed (incl. existingresolve_projecttests, unaffected)cargo clippy/cargo fmt --checkclean on all touched filesAGENTFLARE_BRIDGE_REPOunset — migration 8 applied cleanly to the live~/.agentflare/backend.db, andbridge_repospopulated itself withgetappz/agentflarevia normalresolve_projecttraffic, no manual env var neededSummary by CodeRabbit
New Features
Reliability Improvements