Summary
beads-bv's getNextTask() verifies that bv --robot-next's top pick actually belongs to the configured epic before returning it (falling back to the base beads tracker if not) — but performs no equivalent verification when a label filter is configured. Combined with bv --robot-next/--robot-triage silently ignoring the --label flag (see below), this means a beads-bv tracker configured with labels = [...] can silently hand an agent a task that doesn't carry that label at all, with no error, no fallback, and no indication anything went wrong.
Where
src/plugins/trackers/builtin/beads-bv/index.ts, getNextTask():
// Apply label filter if configured
const labels = this.getLabels();
if (filter?.labels && filter.labels.length > 0) {
args.push('--label', filter.labels[0]!);
} else if (labels.length > 0) {
args.push('--label', labels[0]!);
}
const { stdout, exitCode, stderr } = await execBv(args, this.getWorkingDir());
// ... parsed and returned directly, no re-check against the label ...
Compare to the epic path a few lines later in the same function:
// Verify the selected task belongs to the epic if epicId is set
const epicId = this.getEpicId();
if (filter?.parentId || epicId) {
const parentId = filter?.parentId ?? epicId;
const epicChildren = await this.getCachedEpicChildrenIds(parentId);
if (!epicChildren.includes(nextOutput.id)) {
// bv's top pick isn't in our epic — fall back to base beads
// which filters by epic natively
return super.getNextTask(filter);
}
}
The epic path re-verifies via a real bd list --parent <epicId> query and has a safe fallback. The label path has neither.
Why this matters (contributing root cause, not the only one)
The underlying reason the label path can return an out-of-scope task at all: bv --help (v0.18.0) documents -l/--label as scoping only --robot-insights/--robot-plan/--robot-priority — --robot-next and --robot-triage are not on that list, and empirically bv --robot-next --label <anything> returns the identical top pick regardless of the label value. So beads-bv is passing a flag to bv that bv itself doesn't apply to the code path being used — a bv-side gap. That part may be worth reporting separately against Dicklesworthstone/beads_viewer (or already known there — I didn't find an existing issue).
Filing this one against ralph-tui specifically because even if/when bv fixes its side, beads-bv's lack of a verify-and-fallback step for labels (unlike the existing epic path) is still a real gap — the tracker plugin has the pattern for exactly this kind of defense already built for epics, it's just not applied to labels too.
Impact (real, not hypothetical)
Hit this live: a beads-bv tracker configured with labels = ["repo:site-djbclark"] (part of a multi-controller setup sharing one Beads DB across several repos) returned a task labeled for a different repo. The coding agent proceeded to investigate that other repo's working directory — including evaluating whether to delete a branch there — before being caught by a supervising process. No damage occurred, but nothing in beads-bv itself would have prevented it from going further.
Suggested fix
Apply the same verify-and-fallback pattern already used for epics to labels: after getting bv --robot-next's pick, check the returned task's actual labels (from bd show <id> --json or similar) against the configured label filter, and fall back to super.getNextTask(filter) (which correctly filters by label via bd list --label, per the working listing-path code) if it doesn't match — mirroring exactly what already happens for epicId/parentId.
Workaround in use
Switching from label-based to epic-based scoping — organizing each consumer's tasks under a dedicated epic and using --epic <id> instead of labels = [...], since the epic path has the verification this issue is about. Epic scoping seems to be the officially primary mechanism per the docs (labels are described as "optional and complementary" with minimal documented behavior around the robot-* flags), so this isn't really a workaround so much as switching to the intended-primary path — but it'd be good for the docs and/or a runtime warning to say so explicitly, since nothing currently signals that label-based scoping lacks this safety net.
Summary
beads-bv'sgetNextTask()verifies thatbv --robot-next's top pick actually belongs to the configured epic before returning it (falling back to the basebeadstracker if not) — but performs no equivalent verification when a label filter is configured. Combined withbv --robot-next/--robot-triagesilently ignoring the--labelflag (see below), this means abeads-bvtracker configured withlabels = [...]can silently hand an agent a task that doesn't carry that label at all, with no error, no fallback, and no indication anything went wrong.Where
src/plugins/trackers/builtin/beads-bv/index.ts,getNextTask():Compare to the epic path a few lines later in the same function:
The epic path re-verifies via a real
bd list --parent <epicId>query and has a safe fallback. The label path has neither.Why this matters (contributing root cause, not the only one)
The underlying reason the label path can return an out-of-scope task at all:
bv --help(v0.18.0) documents-l/--labelas scoping only--robot-insights/--robot-plan/--robot-priority—--robot-nextand--robot-triageare not on that list, and empiricallybv --robot-next --label <anything>returns the identical top pick regardless of the label value. Sobeads-bvis passing a flag tobvthatbvitself doesn't apply to the code path being used — abv-side gap. That part may be worth reporting separately againstDicklesworthstone/beads_viewer(or already known there — I didn't find an existing issue).Filing this one against
ralph-tuispecifically because even if/whenbvfixes its side,beads-bv's lack of a verify-and-fallback step for labels (unlike the existing epic path) is still a real gap — the tracker plugin has the pattern for exactly this kind of defense already built for epics, it's just not applied to labels too.Impact (real, not hypothetical)
Hit this live: a
beads-bvtracker configured withlabels = ["repo:site-djbclark"](part of a multi-controller setup sharing one Beads DB across several repos) returned a task labeled for a different repo. The coding agent proceeded to investigate that other repo's working directory — including evaluating whether to delete a branch there — before being caught by a supervising process. No damage occurred, but nothing inbeads-bvitself would have prevented it from going further.Suggested fix
Apply the same verify-and-fallback pattern already used for epics to labels: after getting
bv --robot-next's pick, check the returned task's actual labels (frombd show <id> --jsonor similar) against the configured label filter, and fall back tosuper.getNextTask(filter)(which correctly filters by label viabd list --label, per the working listing-path code) if it doesn't match — mirroring exactly what already happens forepicId/parentId.Workaround in use
Switching from label-based to epic-based scoping — organizing each consumer's tasks under a dedicated epic and using
--epic <id>instead oflabels = [...], since the epic path has the verification this issue is about. Epic scoping seems to be the officially primary mechanism per the docs (labels are described as "optional and complementary" with minimal documented behavior around the robot-* flags), so this isn't really a workaround so much as switching to the intended-primary path — but it'd be good for the docs and/or a runtime warning to say so explicitly, since nothing currently signals that label-based scoping lacks this safety net.