fix: cross-rig prefix routing for issue resolution via routes.jsonl#2954
fix: cross-rig prefix routing for issue resolution via routes.jsonl#2954outdoorsea wants to merge 5 commits intogastownhall:mainfrom
Conversation
When bd runs from a redirected .beads directory (e.g., crew/beercan → town/.beads), issue IDs with prefixes mapped to other rigs in routes.jsonl were not resolved. The local store (hq database) was queried, the issue wasn't found, and only contributor auto-routing was tried as fallback. Add resolveViaPrefixRouting to the resolution chain in routed.go: 1. Try local store (existing) 2. Try prefix routing via routes.jsonl (NEW) 3. Try contributor auto-routing (existing) The prefix router extracts the prefix from the bead ID, looks up the matching route in routes.jsonl, opens the target rig's .beads directory to read its dolt_database from metadata.json, temporarily overrides BEADS_DOLT_SERVER_DATABASE, and opens a read-only store for that database. This unblocks cross-rig operations like bd show, bd update, convoy creation, and auto-dispatch for multi-rig Gas Town deployments. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…arker) The "Dolt Format" doctor check detected pre-0.56 dolt databases but had no automatic fix — it fell through to the default "No automatic fix available" case. The remedy is trivial: create the .bd-dolt-ok marker file, which is exactly what ensureDoltInit does non-destructively. Add a DoltFormat fix function and wire it into the doctor_fix switch. In server mode the .beads/dolt/.dolt/ directory is vestigial; seeding the marker acknowledges the database without any destructive action. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
bd vc commit called store.Commit() which intentionally skips the config table (GH#2455) to prevent sweeping stale config during auto-commits. But bd vc commit is an explicit user action — it should commit everything visible in bd vc status, including config changes from bd config set. Switch to CommitPending which calls CommitWithConfig internally, ensuring all dirty tables including config are committed. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The paths are constructed internally from the beads directory, not from user input. Add nolint:gosec annotations matching the rest of the codebase. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
bd create auto-commits in embedded mode, so a subsequent bd vc commit may find nothing pending. CommitPending correctly returns committed=false in this case. The test now accepts both outcomes, matching the pattern used by commit_with_message. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
All 31 CI checks are now green. The last commit fixes the This PR is the missing piece for cross-rig convoy tracking in gastown. The prefix routing it adds to
Without this, all convoy tracking writes silently fail for cross-rig deps — Ready for review and merge. |
|
This fixes #2967. The Verified that |
hilmes
left a comment
There was a problem hiding this comment.
Review: fix: cross-rig prefix routing for issue resolution via routes.jsonl
Verdict: Three distinct fixes bundled together. The prefix routing is the significant one — functional but has a concurrency hazard. Dolt Format fix and vc commit fix are clean.
5 files, +229 −21. 5 commits. Three independent changes: cross-rig prefix routing (routed.go), Dolt Format auto-fix (dolt_format.go, doctor_fix.go), and vc commit config inclusion (vc.go, vc_embedded_test.go).
Fix 1: Cross-rig prefix routing via routes.jsonl
What it does: Adds a new resolution layer between local store lookup and contributor auto-routing. When a bead ID like hr-8wn.1 isn't found locally, extracts the prefix (hr-), looks it up in routes.jsonl, opens the target rig's database, and resolves there.
Architecture:
extractBeadPrefix→ splits on first hyphen (correct for bead ID format)loadPrefixRoutes→ reads JSONL with comment/blank-line supportresolveViaPrefixRouting→ finds matching route → derives town root → follows redirect → reads target metadata → opens read-only store against target database
Correctness: Functional with caveats.
The resolution chain (local → prefix → auto-routing) is wired into both resolveAndGetIssueWithRouting and getIssueWithRouting, maintaining symmetry. The isNotFoundErr guard ensures prefix routing only fires on genuine not-found errors. The . path skip prevents infinite loops when a prefix routes to the current database.
🔴 Environment variable mutation is not concurrency-safe
origDB := os.Getenv("BEADS_DOLT_SERVER_DATABASE")
_ = os.Setenv("BEADS_DOLT_SERVER_DATABASE", targetDB)
targetStore, err := newReadOnlyStoreFromConfig(ctx, targetBeadsDir)
if origDB != "" {
_ = os.Setenv("BEADS_DOLT_SERVER_DATABASE", origDB)
} else {
_ = os.Unsetenv("BEADS_DOLT_SERVER_DATABASE")
}This is a process-global mutation. If any concurrent goroutine calls GetDoltDatabase() during this window, it reads the wrong database name. In the CLI's current single-threaded command flow this may be safe today, but it's a latent bug waiting for any parallelism (background sync, concurrent commands, test parallelism). The env var hack works around configfile.GetDoltDatabase() reading the env — a cleaner approach would be to pass the database name through to the store constructor directly, but that would require a larger refactor.
Recommendation: At minimum, add a comment warning that this is not goroutine-safe. Ideally, add a WithDatabase(name string) option to newReadOnlyStoreFromConfig or the dolt Config struct to avoid env mutation entirely.
🟡 No tests for prefix routing
The prefix routing is the most complex new code path (~100 lines of logic including file I/O, path resolution, store lifecycle management, env mutation) and has zero test coverage. extractBeadPrefix and loadPrefixRoutes are both easily unit-testable. The integration path is harder but table-driven tests with a mock routes.jsonl would catch regressions.
🟡 loadPrefixRoutes silently swallows malformed lines
if err := json.Unmarshal([]byte(line), &route); err != nil {
continue
}Invalid JSON lines are silently skipped. This is defensible for robustness, but a debug log (gated on BD_DEBUG_ROUTING or debug.Logf) would help diagnose misconfigured routes.jsonl files.
🟡 townRoot derivation assumes specific directory structure
townRoot := filepath.Dir(currentBeadsDir)This assumes currentBeadsDir is always <town_root>/.beads. If the beads directory is elsewhere (symlinked, or BEADS_DIR pointing to a non-standard location), townRoot will be wrong and rigDir will resolve incorrectly. The code should document this assumption.
Minor: Prefix collision
extractBeadPrefix takes the first hyphen: hr-8wn.1 → hr-. If two rigs have prefixes h- and hr-, the ID hr-8wn.1 matches hr- (first hyphen at index 2), which is correct. But h-r-8wn.1 would match h-, not hr-. This is fine as long as prefixes are always the text before the first hyphen — just worth documenting that routes.jsonl prefixes must match this convention.
Fix 2: Dolt Format auto-fix ✅
Clean and minimal. The DoltFormat function:
- Follows redirects to find the actual beads dir
- Checks
IsPreV56DoltDir(guards against overwriting in non-applicable cases) - Writes the
.bd-dolt-okmarker file
The guard is correct — IsPreV56DoltDir returns true only when .dolt/ exists but .bd-dolt-ok doesn't. The fix is idempotent. File permissions (0600) are appropriate.
The doctor_fix.go wiring is a two-line case statement — no risk.
Fix 3: bd vc commit includes config table
Correct behavioral change. Commit() was designed to skip the config table during auto-commits (GH#2455) to prevent sweeping stale issue_prefix changes. But an explicit bd vc commit is a user-initiated action — the user ran bd vc status, saw pending config changes, and expects bd vc commit to commit them.
The switch from Commit() to CommitPending():
- Uses
CommitWithConfiginternally (includes all tables) - Returns
(bool, error)— handles "nothing to commit" gracefully - Generates its own descriptive commit message via
buildBatchCommitMessage
🟡 User's -m message is silently dropped
The old code passed vcCommitMessage to Commit(). The new code calls CommitPending() which calls buildBatchCommitMessage() internally. The user's bd vc commit -m "my message" flag is ignored for the actual Dolt commit message. The PR comment acknowledges this ("Note: CommitPending generates its own descriptive commit message rather than using vcCommitMessage"), but this is a behavioral regression for users who relied on custom commit messages. Worth a follow-up to thread the message through CommitPending, or at minimum document the change.
Test update is correct. The commit_json test now accepts both committed=true and committed=false since bd create auto-commits and CommitPending correctly reports "nothing to commit" when there's nothing pending.
Summary
| Fix | Verdict | Key Concern |
|---|---|---|
| Prefix routing | 🟡 Functional, ship with caveats | Env var mutation not goroutine-safe; no tests |
| Dolt Format | ✅ Clean | None |
| vc commit config | 🟡 Correct intent, subtle regression | User's -m message silently dropped |
Structural note: These three fixes are independent. Consider splitting into separate PRs for cleaner git history and easier revert if any one fix causes issues.
|
Putting this on hold for an architecture discussion. Cross-rig routing was deliberately removed in d762920, and reintroducing it (even at narrower scope) needs consensus before merging. Specific concerns:
The implementation quality is good -- this is not a code quality issue, it is a design direction question. Let us discuss in an issue before proceeding. |
but doesn't this break gastown, since it relies on
yeah this makes sense, i'm just sad bc removing it turned my gastown into a pumpkin after a routine update |
maphew
left a comment
There was a problem hiding this comment.
Reviewed. The three changes are cohesive — routing foundation, doctor migration marker, and vc commit completeness belong together. One rebase note: routed.go will need these imports added (missing from the PR but required by the new functions): bufio, encoding/json, os, path/filepath, github.com/steveyegge/beads/internal/beads, github.com/steveyegge/beads/internal/debug. Routing logic is sound: prefix parsed from bead ID → routes.jsonl lookup → dolt_database read → read-only store opened. All routing/doctor tests pass. ✓
|
@nl0 I'm belaying my approval as I don't understand the situation or concerns @steveyegge raised well enough. I created issue #3055 for the discussion |
Summary
Three fixes for
bd doctorachieving zero-warning state and cross-rig resolution:Cross-rig prefix routing — When
bdruns from a redirected.beadsdirectory, issue IDs with prefixes mapped to other rigs viaroutes.jsonlwere not resolved. AddsresolveViaPrefixRoutingto the resolution chain: local store → prefix routing (NEW) → contributor auto-routing.Dolt Format auto-fix —
bd doctor --fixhad no automatic fix for the "missing .bd-dolt-ok marker" warning. Adds aDoltFormatfix that seeds the marker file.bd vc commitincludes config table —bd vc commitcalledCommit()which skips the config table (GH#2455). An explicit user commit should include everything visible inbd vc status. Switches toCommitPendingwhich includes config.Test plan
bd show hr-8wn.1resolves correctly from redirected contextbd show hq-i91still resolves locally (no regression)BD_DEBUG_ROUTING=1shows prefix routing pathbd doctor --fix --yesnow fixes Dolt Format warningbd config set+bd vc commitclears config fromdolt_statusbd doctorreturns 0 warnings after all fixesCGO_ENABLED=0andCGO_ENABLED=1🤖 Generated with Claude Code