Repo resolution can silently cross git boundaries, and which database was used is unobservable
- Dominant language
- TypeScript
- Stars
- 79
- Forks
- 9
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 42
Description
Found while using tbd across two repos in one agent session. Root resolution behaves as designed in the normal case, but it can silently resolve to the *wrong* repository, and nothing in routine output reveals which database was used.
Worktrees are handled correctly and are not the problem: `.tbd/config.yml` is committed, so `findTbdRoot` stops at the worktree root, and `git rev-parse --git-common-dir` resolves the `gitdir:` file indirection so linked worktrees share one store. Verified working. `paths.ts:55-63` already documents the naive-`.git/tbd` hazard.
## 1. The walk-up crosses git boundaries (the actual bug)
`findTbdRoot()` (`file/config.ts:268-285`) walks `cwd -> dirname -> ...` to the filesystem root looking for `.tbd/config.yml`, with no `.git` sentinel and no depth limit.
Reproduced with scratch repos: `outer/` has `.tbd/` (prefix `out`), and `outer/vendor/inner/` is a **separate git repo with no `.tbd/`**. Running tbd from `inner`:
- resolves to `outer`'s database
- `tbd status` reports `Repository: .../vendor/inner`, `✓ Initialized (.tbd/)`, `ID prefix: out-`
- `tbd list` succeeds and **materializes `outer/.git/tbd/data-sync-worktree`, `layout.yml`, and `locks/`** — a write into a repo the user is not in
- exit 0, no warning
This contradicts the policy settled in tbd-tgwi:
> Policy: tbd always operates on the repository containing cwd.
The repository containing cwd was `inner`; tbd used `outer`. The `GIT_DIR` half of that bug was fixed in #169; the filesystem-walk half is still open. Same class, same policy.
Corollary: a stray `~/.tbd/config.yml` would capture every `tbd` invocation on the machine.
## 2. The ID prefix is ignored on input
`extractShortId()` (`lib/ids.ts:179-181`) strips any alphabetic prefix without comparing it to anything. In a repo whose prefix is `fsq`:
```
tbd show tbd-fiba -> returns fsq-fiba's issue
tbd show zzz-fiba -> returns the same issue
tbd show fsq-zzzz -> Error: Issue not found: fsq-zzzz
```
`extractPrefix()` already exists and is correct; its only caller is `import.ts:714`. Comparing it against `ctx.prefix` in `resolveIssueId()` is a few lines at a single chokepoint, and would turn every cross-repo ID command into a clear error naming both prefixes.
Today a foreign-prefix ID usually surfaces as a confusing `Issue not found`. On a short-id collision (~N/1,679,616 per call) `update`/`close` silently mutate the wrong repo's issue.
## 3. `tbd status` prints cwd under the `Repository:` label
`status.ts:109` sets `working_directory: cwd`; `sections.ts:89` renders it as `Repository:`.
```
$ cd myrepo/docs/project/specs/active
$ tbd status
Repository: /.../myrepo/docs/project/specs/active <- not a repository
$ tbd doctor
Repository: /.../myrepo <- correct
```
`doctor` sets `this.cwd = tbdRoot` first (`doctor.ts:295`) and gets it right. `status` is the command a confused user reaches for, and it is the one that misreports.
## 4. No way to target a repo, and no repo identity in output
- No `--repo` / `--cwd` / `-C` global flag, and no env override. The only way to select a repo is `cd` — which is exactly how this goes wrong, since a `cd` inside a compound shell command persists across `;` separators.
- `--json` carries no repo identity. `list --json` items have `id` and `internalId` but no repo field; `doctor --json` has no absolute repo path at all. An agent cannot assert which database it just read or wrote.
- Among human-readable commands, only `doctor` prints the true resolved root; `prime` prints the prefix. `list`/`ready`/`show`/`search`/`stats`/`create`/`update`/`close` print nothing, and `show` renders the internal `is-` so it carries no prefix signal at all.
## Suggested behavior
1. Keep cwd-based resolution. It matches git and is predictable.
2. **Stop the walk at the git root.** If cwd is inside a git repo and the nearest `.tbd/config.yml` lies outside it, that is a misresolution: error instead of adopting it. This makes the implementation match the tbd-tgwi policy.
3. **Add `tbd -C `**, mirroring `git -C` — the sanctioned way to work across worktrees and forks without `cd`, and what agent tooling should use.
4. **Make identity observable**: `status` should print the resolved root, and every `--json` payload should carry `repo_root` and `id_prefix`.
5. **Validate the prefix on ID input** — the cheapest guardrail, and the only one that catches silent cross-repo *writes*.
Items 2 and 5 make the failure impossible rather than documented; 3 and 4 let callers be explicit and verify.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with findTbdRoot() in file/config.ts, resolveIssueId() and extractShortId() in lib/ids.ts, and the status output paths in status.ts and sections.ts. Trace the existing repository-resolution and ID flows before defining tests for git-boundary handling, prefix validation, explicit repo selection, and observable repo identity in human and JSON output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, typescript
- Domain
- cli, developer-experience
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100