Skip to content

feat(bridge): daemon polls a registry of repos, not one env var - #422

Merged
getappz merged 9 commits into
masterfrom
bridge-multi-repo-registry
Aug 10, 2026
Merged

feat(bridge): daemon polls a registry of repos, not one env var#422
getappz merged 9 commits into
masterfrom
bridge-multi-repo-registry

Conversation

@getappz

@getappz getappz commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a bridge_repos table (migration 0008) — 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's poll loop (github/bridge/runner.rs) now reads that registry fresh each tick and polls every registered repo instead of requiring a single AGENTFLARE_BRIDGE_REPO. Client is now Clone so one GitHub credential is shared across repos in a tick.
  • AGENTFLARE_BRIDGE_REPO still works as a manual one-off override, merged in and deduped against the registry — no regression for existing single-repo/dev setups.
  • Fixes a latent bug as a side effect: build_ctx used to resolve 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. project_id now comes from the registry instead.

Background: found while debugging why a cross-workstation issue (#421, labeled agentflare) sat unclaimed — this workstation's daemon had no AGENTFLARE_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 passed
  • cargo test --bin agentflare github::bridge — 104 passed (incl. new bridge_targets_reads_every_registered_repo)
  • cargo test --bin agentflare mcp_server:: — 185 passed (incl. existing resolve_project tests, unaffected)
  • cargo clippy / cargo fmt --check clean on all touched files
  • Manual: rebuilt, restarted the real daemon on this workstation with AGENTFLARE_BRIDGE_REPO unset — migration 8 applied cleanly to the live ~/.agentflare/backend.db, and bridge_repos populated itself with getappz/agentflare via normal resolve_project traffic, no manual env var needed

Summary by CodeRabbit

  • New Features

    • GitHub bridge repositories are now registered and stored automatically when projects are linked.
    • The bridge can process multiple registered repositories, each with its own project, queue, and work-agent settings.
    • Repository configurations are refreshed as projects are resolved.
  • Reliability Improvements

    • Bridge startup no longer requires a preconfigured repository or project.
    • A temporary failure for one repository no longer prevents others from being processed.
    • Manual repository configuration remains available as a fallback.

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
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The 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.

Changes

GitHub bridge registry

Layer / File(s) Summary
Bridge repository persistence
crates/agentflare-backend/src/bridge_repo.rs, crates/agentflare-backend/src/migrations/0008_bridge_repos.sql, crates/agentflare-backend/src/db.rs, crates/agentflare-backend/src/lib.rs
The backend adds BridgeRepo, the bridge_repos table, ordered listing, and upsert behavior that preserves created_at. Tests cover field mapping, round trips, and updates.
Repository registration
src/mcp_server.rs
Project resolution registers canonical GitHub repositories with project, queue, and optional work-agent settings. Registration is best-effort.
Multi-repository bridge runner
src/github/bridge/runner.rs, src/github/client.rs
The bridge discovers registered repositories on each tick, applies repository-specific settings, handles repositories independently, and retains the environment variable as a fallback. Client now supports cloning with a shared connection pool.

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
Loading

Possibly related PRs

  • getappz/agentflare#162: Introduced backend and MCP infrastructure extended by bridge repository persistence and registration.
  • getappz/agentflare#379: Introduced the GitHub bridge extended here with persistent multi-repository discovery.
  • getappz/agentflare#412: Added related bridge configuration and workflow behavior used by the multi-repository runner.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: polling a repository registry instead of one environment-variable repository.
Description check ✅ Passed The description explains the changes, rationale, compatibility behavior, and test results, but omits the optional Notes for reviewers section.
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 bridge-multi-repo-registry

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9c47be7 and d84b1ba.

📒 Files selected for processing (7)
  • crates/agentflare-backend/src/bridge_repo.rs
  • crates/agentflare-backend/src/db.rs
  • crates/agentflare-backend/src/lib.rs
  • crates/agentflare-backend/src/migrations/0008_bridge_repos.sql
  • src/github/bridge/runner.rs
  • src/github/client.rs
  • src/mcp_server.rs

Comment on lines +61 to +71
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,
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ 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.

Suggested change
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.

Comment on lines +246 to +251
let _guard = agent_registry::detect::PATH_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
unsafe {
std::env::remove_var("AGENTFLARE_BRIDGE_REPO");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Comment thread src/mcp_server.rs
Comment on lines +956 to +963
let _ = agentflare_backend::bridge_repo::upsert(
conn,
&repo_id.to_string(),
project_id,
&folder_path.to_string_lossy(),
&queue_label,
None,
crate::claims::now(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ 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.

shiva added 2 commits August 10, 2026 15:12
Agentflare-Agent: claude-code
Agentflare-Branch: bridge-multi-repo-registry
Agentflare-Agent: claude-code
Agentflare-Branch: bridge-multi-repo-registry
@getappz
getappz enabled auto-merge (squash) August 10, 2026 09:50
@getappz
getappz merged commit 7bffe02 into master Aug 10, 2026
16 checks passed
@getappz
getappz deleted the bridge-multi-repo-registry branch August 10, 2026 11:35
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