Skip to content

fix(agent-inbox): number resolve against file position, not the pending view - #2739

Open
davidnunez wants to merge 1 commit into
santifer:mainfrom
davidnunez:fix/agent-inbox-stable-numbering
Open

fix(agent-inbox): number resolve against file position, not the pending view#2739
davidnunez wants to merge 1 commit into
santifer:mainfrom
davidnunez:fix/agent-inbox-stable-numbering

Conversation

@davidnunez

@davidnunez davidnunez commented Aug 12, 2026

Copy link
Copy Markdown

The bug

resolve N indexes into the pending subset:

// Number against the pending view, so `list` then `resolve N` line up.
const pending = parseItems().filter((it) => !it.done);
const target = pending[n - 1];

Every resolve removes an item from that subset, so every higher number shifts down by one.
Read list once, then act on what you read, and the results land on the wrong items:

Verbatim run against main, five queued items, list read once:

$ node agent-inbox.mjs list
 1. [ ] alpha
 2. [ ] bravo
 3. [ ] charlie
 4. [ ] delta
 5. [ ] echo

$ node agent-inbox.mjs resolve 2 --result "R-2"
Resolved #2: bravo

$ node agent-inbox.mjs resolve 3 --result "R-3"
Resolved #3: delta            <-- list said #3 was charlie

$ node agent-inbox.mjs resolve 4 --result "R-4"
agent-inbox.mjs: no pending item #4 (3 pending)

Resulting queue:

- [ ] alpha
- [x] bravo → result: R-2
- [ ] charlie                 <-- skipped, still pending
- [x] delta → result: R-3     <-- carries charlie's result
- [ ] echo

Three distinct failures from one snapshot: charlie is silently skipped, delta is stamped
with a result it did not produce, and the third command dies with no pending item #4 (3 pending) moments after list displayed five items. The second is the dangerous one — it
prints Resolved #3 and exits 0.

Why this is the designed usage, not misuse

modes/agent-inbox.md tells the agent to drain the queue top-to-bottom and mark each item as
it goes:

  1. Run each unchecked item top-to-bottom…
  2. After each, mark it [x] and append → result: <one line> — either by hand or with
    node agent-inbox.mjs resolve <n> --result "...".

An agent reads list once into context, works the batch, and resolves against the numbers it
was shown. Re-running list between every resolve is the only safe pattern under the old
behavior, and nothing in the docs said so — the code comment claimed the opposite ("so list
then resolve N line up"), which is true for exactly one resolve.

The failure mode is the worst kind for this file: data/agent-inbox.md is a provenance log.
A wrong result line is indistinguishable from a right one after the fact.

The fix

Number against the full item list. add only ever appends, so an item's file position never
changes meaning once printed, and a batch read off one list stays correct regardless of
order or how many land in between.

list keeps showing pending-only by default, so numbers now have gaps (1, 3, 5) as items
resolve. That reads as a display bug unless you say otherwise, so list prints a footer:

(3 resolved item(s) hidden — numbers are stable file positions, so gaps are expected. `list --all` shows everything.)

Two guards on the write path

A stale number from an older list is the residual risk, so the write refuses in the two
cases where it is most likely wrong:

  1. Re-resolving an already-[x] item aborts instead of overwriting its result. This is
    what a stale number most often lands on, and silently re-stamping destroys the earlier
    provenance line.
  2. --expect "<substring>" aborts unless the target item contains that text
    (case-insensitive). Turns "right command, wrong row" into an error. A valueless --expect
    fails rather than silently disabling the guard.

Both print the offending item's text, so the error shows you what you almost hit.

modes/agent-inbox.md now instructs the draining agent to pass --expect every time.

Happy to split --expect into a follow-up PR if you'd rather keep this one purely a fix —
it's bundled because it's the hardening for this specific failure, and the flag is opt-in and
backward-compatible.

Tests

agent-inbox-tests.mjs gains three cases:

  • 7 — the regression proper. Queue five items, snapshot list once, then fire four
    resolves against that snapshot with no re-listing. Asserts each result landed on the item it
    named, the survivor kept its original number, and the footer explains the gap. Fails against
    the old code with results on the wrong items.
  • 8 — re-resolving a done item exits 1, says why, and leaves the original result intact.
  • 9--expect mismatch exits 1 and writes nothing; valueless --expect exits 1 and
    writes nothing; a case-insensitive match resolves normally.

Plus a runFail helper for exit-code assertions.

node agent-inbox-tests.mjs33 passed, 0 failed (16 on main).

node test-all.mjs on a clean checkout of this branch → 3429 passed, 0 failed, 2 warnings
(both environmental: no user cv.md, and no Go compiler for the dashboard build).

Relationship to #2614

#2614 (open) fixes a different bug in the same file — concurrent add losing items — and
touches only add and its test. No logical overlap with this change; a textual conflict is
possible if both land. Happy to rebase on top of it, in either order.

Docs

docs/SCRIPTS.md gains rows for list and resolve (neither was registered).

Summary by CodeRabbit

  • New Features

    • Added stable inbox item numbers that remain consistent as earlier items are resolved.
    • Added optional case-insensitive --expect validation when resolving items.
    • Added clearer reporting of hidden resolved-item gaps and stamped resolution results.
  • Bug Fixes

    • Prevented resolving missing or already-completed items.
    • Improved batch resolution behavior using a single list snapshot.
  • Documentation

    • Documented inbox listing, resolution, stable numbering, validation, result stamping, and failure scenarios.

@github-actions

Copy link
Copy Markdown
Contributor

Welcome to career-ops, @davidnunez! Thanks for your first PR.

A few things to know:

  • Tests will run automatically — check the status below
  • Make sure you've linked a related issue (required for features)
  • Read CONTRIBUTING.md if you haven't

We'll review your PR soon. Join our Discord if you have questions.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The inbox CLI now assigns stable full-file positions, preserves gaps after resolution, rejects invalid or completed targets, and supports case-insensitive --expect checks. Tests and documentation cover the updated resolution workflow.

Changes

Inbox resolution safeguards

Layer / File(s) Summary
Stable numbering and guarded resolution
agent-inbox.mjs
The CLI records full-file positions, preserves gaps for resolved items, validates targets, rejects completed items, and supports --expect.
Resolution regression coverage
agent-inbox-tests.mjs
Tests cover batch resolution, duplicate resolution rejection, failed expectation checks, valueless options, and case-insensitive matches.
Documented guarded workflow
docs/SCRIPTS.md, modes/agent-inbox.md
Documentation describes stable numbers, resolved-item gaps, required expectation guards, and result stamping.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: darkpandawarrior

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The changes address agent-inbox behavior and do not implement the scoring-model or contacto requirements in [#2] and [#3]. Implement the requirements for [#2] and [#3], or link the PR to issues that cover the agent-inbox changes.
Out of Scope Changes check ⚠️ Warning The PR changes agent-inbox code, tests, and documentation, which are unrelated to the linked scoring-model and contacto objectives. Remove the agent-inbox changes or update the linked issues to match the PR scope.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: stable resolve numbering based on file position.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent-inbox-tests.mjs`:
- Around line 169-171: Update the list-output assertions in the relevant inbox
test to leave pending items after resolved items and verify that they retain
their original numbers, including visible gaps such as items 3 and 5. Do not
only assert item 1; preserve the existing resolved-items-hidden assertion.

In `@agent-inbox.mjs`:
- Around line 139-155: Make the resolve operation atomic by wrapping its
re-read, validation, update, and inbox-file replacement in the repository’s
shared lock, using the existing lock mechanism rather than adding a new one.
Move the already-resolved and --expect checks to operate on the locked re-read,
and update add to acquire that same lock so concurrent writes cannot overwrite
each other.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 46fe0bf9-890c-4c34-9331-0eba33d2dd36

📥 Commits

Reviewing files that changed from the base of the PR and between 350ab73 and ce2210a.

📒 Files selected for processing (4)
  • agent-inbox-tests.mjs
  • agent-inbox.mjs
  • docs/SCRIPTS.md
  • modes/agent-inbox.md

Comment thread agent-inbox-tests.mjs
Comment thread agent-inbox.mjs
Comment on lines +139 to +155
const items = parseItems();
const target = items[n - 1];
if (!target) {
const pending = items.filter((it) => !it.done).length;
fail(`no item #${n} — inbox has ${items.length} item(s), ${pending} pending. Run \`list --all\`.`);
}
// Already-resolved is an error, not a silent re-stamp: it is what a stale
// number from an older `list` most often lands on.
if (target.done) fail(`item #${n} is already resolved — refusing to overwrite it:\n #${n}: ${target.text}`);
// Optional caller-side guard: abort unless the target says what the caller
// thinks it says. Catches "right command, wrong target" generally.
if (hasOpt('expect')) {
const expect = opt('expect');
if (!expect) fail('--expect needs a substring, e.g. --expect "Dana-Farber"');
if (!target.text.toLowerCase().includes(expect.toLowerCase())) {
fail(`item #${n} does not contain --expect ${JSON.stringify(expect)} — refusing to resolve:\n #${n}: ${target.text}`);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make the resolve write atomic.

Two resolve processes can parse and validate the same unchecked item before either process writes. The last writeFileSync() then discards the other result. The already-resolved and --expect checks do not prevent this race.

Use the repository shared lock pattern. Re-read, validate, update, and atomically replace the inbox file while that lock is held. Ensure add uses the same lock.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent-inbox.mjs` around lines 139 - 155, Make the resolve operation atomic by
wrapping its re-read, validation, update, and inbox-file replacement in the
repository’s shared lock, using the existing lock mechanism rather than adding a
new one. Move the already-resolved and --expect checks to operate on the locked
re-read, and update add to acquire that same lock so concurrent writes cannot
overwrite each other.

…ng view

`resolve N` indexed into the *pending* subset, so every resolve shifted all
higher numbers down by one. Reading `list` once and firing a batch of resolves
off that snapshot stamped results onto the wrong items — silently, with no
error.

Number against the full item list instead. `add` only ever appends, so a file
position never changes meaning once printed; `list` now shows gaps as items
resolve and prints a footer so the gaps don't read as a display bug.

Two guards on the write path: re-resolving a done item aborts instead of
overwriting its result (that is where a stale number most often lands), and
--expect "<substring>" aborts unless the target contains that text. A valueless
--expect fails rather than silently disabling the guard.

Tests 8-10: batch-of-resolves regression against one snapshot, re-resolve
refusal, and --expect mismatch/valueless/case-insensitive-match.

Rebased onto main after santifer#2614 merged; the atomic-append fix is preserved and
this change touches only parseItems/list/resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxADPBJCghNfm5jPZ2Phhh
@davidnunez
davidnunez force-pushed the fix/agent-inbox-stable-numbering branch from ce2210a to e49d714 Compare August 12, 2026 11:20
@davidnunez

Copy link
Copy Markdown
Author

Rebased onto main now that #2614 has merged — the branch was conflicting against the atomic-append change.

The rebase preserves #2614 in full; this PR touches only parseItems/list/resolve, and the concurrent-add test is untouched and still passing. My three cases renumber to 8–10 behind it.

node agent-inbox-tests.mjs → 36 passed, 0 failed. node test-all.mjs → 3576 passed, 0 failed (2 environmental warnings: no user cv.md, no Go compiler for the dashboard build).

Also trimmed the description: it previously cited a file that isn't part of this repository as prior art. The reasoning stands without it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/SCRIPTS.md (1)

818-842: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Do not hardcode ranking metrics in this Markdown file.

The section embeds the /5 scale, the default and maximum limits, and the CV excerpt size. Generate these values from the canonical configuration or runtime help output so the documentation does not drift from evaluation behavior.

As per coding guidelines, **/*.{md,html,tex} says: “NEVER hardcode metrics -- read them from these files at evaluation time.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/SCRIPTS.md` around lines 818 - 842, Update the ranking documentation to
avoid hardcoded evaluation metrics, including the /5 scale, default and maximum
--limit values, and CV excerpt length. Generate these values from the canonical
ranking configuration or runtime help output, following the repository guidance
for Markdown metric values, so the documented behavior stays synchronized with
evaluation.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent-inbox.mjs`:
- Around line 190-192: Update the --expect handling in the resolve option
parsing flow so every occurrence is validated, rather than relying on
opt('expect') reading only the first value. Reject any valueless --expect,
including trailing duplicates, or reject duplicate --expect options before
resolving the target while preserving valid single-option behavior.

---

Outside diff comments:
In `@docs/SCRIPTS.md`:
- Around line 818-842: Update the ranking documentation to avoid hardcoded
evaluation metrics, including the /5 scale, default and maximum --limit values,
and CV excerpt length. Generate these values from the canonical ranking
configuration or runtime help output, following the repository guidance for
Markdown metric values, so the documented behavior stays synchronized with
evaluation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6f9672e5-eae0-44cf-8585-15aa150dbe88

📥 Commits

Reviewing files that changed from the base of the PR and between ce2210a and e49d714.

📒 Files selected for processing (3)
  • agent-inbox-tests.mjs
  • agent-inbox.mjs
  • docs/SCRIPTS.md

Comment thread agent-inbox.mjs
Comment on lines +190 to +192
if (hasOpt('expect')) {
const expect = opt('expect');
if (!expect) fail('--expect needs a substring, e.g. --expect "Dana-Farber"');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject every valueless --expect occurrence.

opt('expect') reads only the first occurrence. Therefore, resolve 1 --expect "valid" --expect --result "..." succeeds instead of rejecting the trailing valueless option.

Validate all occurrences, or reject duplicate --expect options before resolving the target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent-inbox.mjs` around lines 190 - 192, Update the --expect handling in the
resolve option parsing flow so every occurrence is validated, rather than
relying on opt('expect') reading only the first value. Reject any valueless
--expect, including trailing duplicates, or reject duplicate --expect options
before resolving the target while preserving valid single-option behavior.

@santifer

Copy link
Copy Markdown
Owner

Heads-up, @davidnunez: this conflicts now and the cause is mine. Last night I pushed a fix to agent-inbox.mjs (a7f65b6) that routes the append through the shared pipeline lock, because the concurrent-add test was losing exactly one item of 30 on Windows. That lands in the same file you're changing.

Don't rebase yet. Your change is about how resolve N numbers its target, which is an independent question from the locking, and I want to review it properly before you spend time on a merge. I'll tell you when the base is settled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants