Skip to content

fix: harden task listing against partial and out-of-range results - #120

Merged
alDuncanson merged 1 commit into
mainfrom
fix/harden-task-listing
Sep 8, 2026
Merged

alDuncanson merged 1 commit into
mainfrom
fix/harden-task-listing

Conversation

@alDuncanson

Copy link
Copy Markdown
Owner

Follow-ups from #112, found while reviewing the merged ListTasks implementation.

Partial listings were indistinguishable from complete ones

list_all_tasks has three defenses against a misbehaving server, and all three ended the crawl by returning whatever had accumulated. A caller got a short list with no way to tell it apart from a genuinely complete one.

It now returns a TaskListing — the tasks plus a truncated flag:

  • CLI text: a trailing Warning: listing stopped early; showing N task(s), which may not be all of them.
  • CLI --output json: a truncated field in the envelope
  • CLI --output ndjson: a trailing {"type": "truncated", "count": N} record, matching how error records already work
  • MCP list_tasks: a truncated field alongside count

The token guard missed cycles longer than one

The loop only stopped when a server handed back the token just sent. A server alternating A → B → A never repeats consecutively, so it ran to the 1000-page cap. The loop now stops as soon as a page contributes no new task while still offering a token, which subsumes replayed pages and token cycles of any length.

The hardcoded default page size was based on a wrong premise

The old comment said servers "commonly reject a zero page size and some treat an unset field as zero, so always send an explicit one," and sent 50 whenever the caller omitted it.

page_size and history_length are proto3 optional fields with real presence. Leaving them unset omits them from the wire entirely — it does not send zero. Confirmed against a live agent: an unset page size returns the full listing, while an explicit 0 is rejected with minimum page size is 1. The default is gone; omitting the flag now lets the server choose, which is what the docstring always claimed.

Validation was duplicated and incomplete

Three copies of the page-size check lived in the service, the CLI, and the MCP tool. They are now validate_page_size and validate_history_length in common/input_validation.py, with a label parameter so the CLI still names the flag the user typed while details.field keeps the machine name.

Two gaps closed along the way:

  • page_size above 100 is now rejected locally. The spec bounds a page to [1, 100]; previously a caller paid a round trip to learn that.
  • get_task never validated history_length at all.

Testing

Full suite green (520 passed), plus ruff, format, and ty.

Exercised end to end against a live A2A agent (SDK DefaultRequestHandlerV2 + InMemoryTaskStore, JSON-RPC):

  • task list with and without --page-size, including real multi-page crawls — a paginated listing returns the same set as an unpaginated one
  • Bounds probed directly against the server: 0minimum page size is 1, 101maximum page size is 100, 100 → OK, unset → OK. The new local checks match the server exactly.
  • Truncation confirmed live: capped at 2 pages of 2, a 6-task agent returns 4 tasks with truncated=True; uncapped returns 6 with truncated=False
  • MCP list_tasks, get_task, send_message against the same agent, plus all three validators rejecting correctly
  • CLI task get, --status filtering, --history-length, and ndjson output

The TUI path is unchanged by this PR and has not been manually exercised.

🤖 Generated with Claude Code

Follow-ups from #112.

list_all_tasks silently returned partial results. When a pagination
defense stopped the crawl early, callers got a short list with no way to
tell it apart from a complete one. It now returns a TaskListing carrying
the tasks plus a truncated flag, surfaced by the CLI in all three output
formats and by the MCP tool as a `truncated` field.

The loop also stops on any non-progressing page rather than only on an
immediately repeated token, so a server cycling two or more tokens is
caught on the first page that contributes nothing instead of spinning to
the page cap.

Drop the hardcoded 50-task default page size. page_size and
history_length are proto3 optional fields with real presence, so leaving
them unset genuinely omits them and the server applies its own default;
the comment claiming otherwise was wrong. Verified against a live agent.

Validation moves into shared validate_page_size and
validate_history_length helpers, used by the service, CLI, and MCP
instead of three copies. page_size now also rejects values above 100,
the spec maximum, which a server would only reject after a round trip.
history_length is validated on get_task as well, which had no check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alDuncanson
alDuncanson marked this pull request as ready for review September 8, 2026 18:35
@alDuncanson
alDuncanson merged commit dea4ef8 into main Sep 8, 2026
4 checks passed
@alDuncanson
alDuncanson deleted the fix/harden-task-listing branch September 8, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant