fix(batches): resolve batch ids beyond the 100-batch cap (#825)#872
Conversation
…#825) The four 'cf work batch status/stop/resume/follow' commands resolved a partial batch id in Python over conductor.list_batches(workspace, limit=100). Any batch beyond position 100 (oldest, since list_batches orders newest-first) was unreachable — the same class of bug as #743 for tasks. Changes: - conductor.py: add find_batch_by_prefix() — SQL LIKE with no LIMIT, escaping LIKE metachars (\ % _) the same way tasks.find_by_prefix does. - cli/app.py: replace all four list_batches(limit=100) + Python-filter patterns with conductor.find_batch_by_prefix(). - tests/core/test_batch_truncation.py: regression suite (150 batches, prefix resolution, empty result, ambiguity, LIKE metachar escaping, backslash escaping) — mirrors test_tasks_truncation.py. Closes #825
|
Claude finished @frankbria's task in 7m 47s —— View job I'll analyze this and get back to you. |
ReviewThis is the first review on this PR (no prior reviews/comments to reconcile with — just the automated "Claude Code is working" placeholder). Solid, narrowly-scoped fix that mirrors the accepted Verified:
Minor, non-blocking observations:
Nothing here blocks merging — the core fix is correct, well-tested, and consistent with the established |
Cross-family review — opencode (zai/glm-5.2)Adversarial Review — PR #872I traced all four call sites and confirmed the wiring against FindingsCritical — Major — Major — Major — Suggestion — Suggestion — Nitpick — Notes that are not findings
VerdictREQUEST_CHANGES — the |
Cross-family review (#872) found that removing the 100-batch resolution cap widens the collision surface: an ambiguous short prefix is now checked against the entire batch history. The four batch commands silently acted on matching[0] (batch_stop mutating with no guard; batch_resume warned then proceeded), diverging from the task-side pattern that errors + exits. Add the len(matching) > 1 -> 'Multiple batches match' + typer.Exit(1) guard to status/stop/resume/follow, mirroring tasks resolution. Adds a parametrized CliRunner regression test seeding two prefix-colliding batches. Also fixes a test class-name typo (Beyon100 -> Beyond100).
|
Claude finished @frankbria's task in 3m 38s —— View job GLM precision review — PR #872 (re-review after
|
| Area | Result |
|---|---|
find_batch_by_prefix SELECT column order (conductor.py:855) |
Matches _row_to_batch (2441), list_batches (797), _save_batch (2410) — all 17 columns in the same positions ✓ |
LIKE escape order \→%→_ (conductor.py:849) |
Correct; backslash-first avoids double-escaping the escapes it adds. Mirrors accepted tasks.find_by_prefix:248 ✓ |
| Injection / param binding (conductor.py:860-863) | Parameterized (workspace.id, f"{escaped}%"); prefix never string-interpolated into SQL ✓ |
| Connection lifecycle (conductor.py:850-867) | Closed in finally, no leak ✓ |
ORDER BY started_at DESC NULL concern |
started_at is TEXT NOT NULL on batch_runs (workspace.py:296, 518) — concern moot ✓ |
| Ambiguity guards, all 4 sites (app.py:4027, 4177, 4251, 4449) | Each does len(matching) > 1 → "Multiple batches match" + typer.Exit(1), mirroring the task-side pattern at app.py:2484. grep confirms no unguarded matching[0] remains for batches; the old list_batches(workspace, limit=100) + startswith filter is fully gone ✓ |
b.status.value in error output |
BatchStatus(str, Enum) (conductor.py:526) — .value is valid ✓ |
| Test suite validity | _save_batch (conductor.py:2407) and workspace.repo_path both exist; the parametrized CLI ambiguity test seeds a real colliding pair and asserts exit_code == 1 ✓ |
Already-posted findings — not repeated: ambiguity handling on stop/resume/status/follow (raised by both prior reviews) is fully resolved by 8b2a7d9. SQLite ASCII case-insensitivity of LIKE and the unbounded (no-LIMIT) read were both flagged earlier; both are consistent with the accepted tasks.find_by_prefix precedent and are not new defects in this PR.
The implementation is a faithful, correct mirror of the accepted #743/#824 task-resolution pattern, with the collision-surface gap from the prior cross-family review now closed on all four mutating + read-only commands.
Follow-up review — second commit (8b2a7d9)Re-reviewing after the cross-family (opencode) adversarial review and the fix commit that followed it. Confirmed: the REQUEST_CHANGES-blocking findings are resolved.
One suggestion from the opencode review is moot, not just unaddressed: the concern about Still open, still non-blocking (same suggestion as before, not re-addressed):
Nothing else to add beyond what's already been covered across the two rounds of review — the core fix, the escaping, and the test coverage were solid from the first commit; the ambiguity-handling gap the second commit closes was the one thing worth blocking on, and it's closed correctly with test coverage to prove the wiring, not just the function. No remaining objections from me. |
Merge triage summary (final gate)Cross-family review: opencode (zai/glm-5.2) — entered on an existing PR without a prior Phase-7 gate, so the post-PR cross-family pass was run now and posted above. Verdict was REQUEST_CHANGES (Critical on Findings triage:
Demo (Phase 11) — all acceptance criteria verified against the real CLI on a 156-batch workspace with 4 named targets seeded at positions 153–156 (beyond the old 100-cap): All CI green on |

Summary
Fixes #825 — the same class of truncation bug as #743, applied to the
Batchentity.The four
cf work batch status/stop/resume/followcommands resolved a partialbatch id in Python over
conductor.list_batches(workspace, limit=100). Anybatch beyond position 100 (the oldest ones, since
list_batchesordersnewest-first) was unreachable by partial id — the user would get "No batch
found" even though the batch existed.
What changed
codeframe/core/conductor.py— newfind_batch_by_prefix(workspace, prefix):SQL
LIKE 'prefix%' ESCAPE '\'with noLIMIT, mirroringtasks.find_by_prefix.LIKE metachars (
\,%,_) in the user-typed prefix are escaped beforethe query so a stray wildcard cannot over-match.
codeframe/cli/app.py— all four resolution sites replaced:list_batches(workspace, limit=100)+ Pythonstartswithfilter →find_batch_by_prefix(workspace, batch_id).tests/core/test_batch_truncation.py— regression suite (6 tests),mirrors
test_tasks_truncation.py:%,_) → empty (no over-match)Acceptance Criteria
cf work batch status <partial-id>resolves batches beyond position 100cf work batch stop <partial-id>resolves batches beyond position 100cf work batch resume <partial-id>resolves batches beyond position 100cf work batch follow <partial-id>resolves batches beyond position 100Test Plan
test_tasks_truncation.py(3 tests),test_batch_run_connection_safety.py(13 tests) — all greenKnown Limitations / Intentionally Deferred
list_batchesdefault limit of 20 (and thelimit=100used in the oldresolution path) is unchanged —
find_batch_by_prefixis the resolutionpath only. Unbounded batch listing (analogous to
list_tasks(limit=None))is not addressed here; that is a separate concern if needed.
Implementation Notes
The fix is intentionally minimal — same shape as the tasks fix in #824 so the
pattern is easy to verify. No behavior changes beyond making batches 101+
addressable by partial id.
Closes #825