Skip to content

beads tracker: getNextTask silently returns no_tasks when tracker data lives outside the cwd #397

Description

@seniorquico

System Information

ralph-tui: 0.12.0
runtime: bun 1.3.14
os: win32 10.0.26200 (x64)
agent: opencode v1.17.15
tracker: beads
global-config: yes
project-config: no
templates: beads-bv.hbs, beads-rust-bv.hbs, beads-rust.hbs, beads.hbs, default.hbs, jira.hbs, json.hbs
bundled-skills: 4
skills-installed: claude:0, opencode:0, droid:0, gemini:0, codex:0, kiro:0, cursor:0, github-copilot:0, kimi:0, pi:0

Steps to reproduce

Setup:

  • bd 1.1.0 with a Dolt-server backend, located via the BEADS_DIR env var.
  • The working directory has NO local .beads/ directory (tracker data is on the
    Dolt server, not in the repo/worktree).
  • A global ~/.config/ralph-tui/config.toml (below); no project .ralph-tui/config.toml.
  • An epic with two open child tasks.
  1. From the working directory, confirm bd resolves ready work via BEADS_DIR:

    $ bd ready --parent <epic-id> --json
    # -> returns both open child tasks (see logs)
  2. Run ralph-tui headless, scoped to the epic, from that same directory:

    $ ralph-tui run --epic <epic-id> --cwd /path/to/workdir --headless --no-setup --iterations 0 --serial

Expected:

Ralph selects a ready task and runs an iteration (bd shows the tasks as ready from the same cwd/env).

Actual:

Ralph resolves the epic and reports "Total tasks: 2", then immediately stops with reason "no_tasks" and 0 iterations. No task is ever selected.

Root cause (source @ commit 8191b80):

In src/plugins/trackers/builtin/beads/index.ts, getNextTask() starts with if (!(await this.isReady())) return undefined;. isReady() -> detect() requires access(join(this.workingDir, this.beadsDir /* ".beads" */), R_OK), i.e. a local <cwd>/.beads directory. With BEADS_DIR set there is no local .beads/, so detect() returns available:false and getNextTask() returns undefined (with no log). getTasks() and getEpics() do NOT call isReady(), so enumeration works while selection silently yields nothing — hence "Total tasks: 2" followed by "no_tasks". The engine (src/engine/index.ts) emits reason "no_tasks" when getNextTask returns null.

Relevant logs / terminal output

### ralph-tui run (headless):


Epic: <epic-id>
[INFO] [session] Session <session-id> created. Agent: opencode, Tracker: beads
[INFO] [engine] Ralph started. Total tasks: 2
[INFO] [engine] Ralph stopped. Reason: no_tasks. Iterations: 0, Tasks completed: 0
[INFO] [engine] Ralph stopped. Reason: interrupted. Iterations: 0, Tasks completed: 0
  Tasks: 0/2 completed


### .ralph-tui/session.json shows both tasks WERE loaded (so the tracker resolved):


"trackerState": {
  "plugin": "beads",
  "epicId": "<epic-id>",
  "totalTasks": 2,
  "tasks": [
    { "id": "<epic-id>.1", "title": "", "status": "open", "completedInSession": false },
    { "id": "<epic-id>.2", "title": "", "status": "open", "completedInSession": false }
  ]
}


### For contrast, `bd ready` from the SAME cwd + env returns those tasks:


$ bd ready --parent <epic-id> --json
[
  { "id": "<epic-id>.1", "status": "open", "issue_type": "task", ... },
  { "id": "<epic-id>.2", "status": "open", "issue_type": "task", ... }
]

Configuration (if relevant)

agent = "opencode"
tracker = "beads"
configVersion = "2.1"
maxIterations = 10
autoCommit = true

[agentOptions]
provider = "<provider>"
model = "<model>"
variant = "<variant>"

Additional context

  • Inconsistency: getNextTask() gates on isReady(); getTasks()/getEpics() do not. Same tracker, same run — enumeration works, selection doesn't.
  • Silent failure: the isReady() false path is an unlogged return undefined, while the other getNextTask failure branches (non-zero exit, JSON parse error) do log. So the one path that trips a valid config emits no diagnostics.
  • Wrong readiness signal: bd is explicitly designed to locate its tracker outside the cwd via BEADS_DIR (and --db). detect() using filesystem presence of <cwd>/.beads rejects a configuration that bd fully supports.
  • The adapter has a beadsDir config option, but detect() does join(workingDir, beadsDir), so pointing it at an absolute/external path doesn't work.

Suggested fixes (any one):

  1. In detect(), treat BEADS_DIR / an absolute beadsDir / a configured --db as sufficient (don't join those with cwd).
  2. Drop the isReady() gate in getNextTask for parity with getTasks/getEpics (let bd be the source of truth).

Workaround (works today): create an empty <cwd>/.beads/ directory. It satisfies detect() and does NOT shadow the Dolt server — with BEADS_DIR set, bd still uses the server even when an empty local .beads is present.

Why it matters: this blocks using ralph-tui with a shared/remote beads (Dolt) tracker that lives independently of each git repo/worktree — a setup that avoids committing tracker files into repos you don't own.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions