fix(mcp): make axme_finalize_close required-field errors agent-actionable#145
Merged
George-iam merged 1 commit intoJun 3, 2026
Merged
Conversation
…able
The schema for axme_finalize_close declares 6 handoff strings as
required z.string() (stopped_at, summary, in_progress, next_steps,
worklog_entry, startup_text). When an agent omits any of them Zod
emits "Expected string, received undefined" per missing field — which
has been mis-read as a per-field server bug rather than a missing
argument.
Two changes:
- Add .min(1, "<actionable message>") to all six fields. The message
names the field, marks it REQUIRED, and gives an empty-state
placeholder ("(nothing in progress)" / "(none — work is complete)")
so the agent knows what to pass when there's literally nothing to
report. No behavior change for valid (non-empty-string) calls.
- Mark each field as [REQUIRED] / [optional] in the .describe() text
and in the axme_begin_close checklist output. Required block now
carries an explicit "ALL must be present and non-empty" warning
with omit-is-error rationale.
608/608 tests pass; type-check clean; bundle builds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When an agent calls
axme_finalize_closeand omits any of the six required handoff strings (stopped_at,summary,in_progress,next_steps,worklog_entry,startup_text), Zod emits per-field\"Expected string, received undefined\"errors. Multiple agents have mis-read this as a per-field server bug rather than "you forgot to pass these arguments."Most recent occurrence: another session's agent ran into the error, gave up after 4 retries with different content in the two fields it was already passing, and reported it as a server-side parser bug affecting four specific long-string fields.
Diagnosis
The schema is symmetric — all six handoff strings are plain
z.string()with.describe(), no per-field preprocess, transform, or size limit. The error shape is real (Zod genuinely returns four errors when four required fields are missing), but the root cause is the missing payload, not server-side handling.axme_begin_close's checklist output listed the six fields without marking them required, and the schema's.describe()text did not signal that omitting was an error. So the call-site contract was ambiguous → agent omitted → Zod fired multiple errors → agent inferred wrong cause.Fix
Two surface-level changes, no behavior change for any valid call:
1. Actionable Zod messages with
.min(1)Each of the six required strings now uses
.min(1, \"...\")with a custom message that:\"in_progress is REQUIRED — ...\").REQUIRED.\"(nothing in progress)\",\"(none — work is complete)\",\"- (no items)\").Example new message:
2. Mark required vs optional in
axme_begin_closechecklistThe handoff section in the checklist output now splits into two blocks:
prs,test_results,blockers,dirty_branches.Each
.describe()text also leads with[REQUIRED]or[optional]so the schema-rendered tool docs match the checklist.What this fixes
Expected string, received undefined(4×)in_progress is REQUIRED — pass branches / PRs / uncommitted work, or '(nothing in progress)' if the working tree is clean.(per field)in_progress: current state (branches, PRs, uncommitted work)"in_progress(REQUIRED) — branches, PRs, uncommitted work. Empty placeholder:\"(nothing in progress)\""Behaviour change
For valid calls (non-empty strings for all six): no change. Same code path, same outputs.
For invalid calls that pass an empty string
\"\"for a required field: now rejected with the new actionable message instead of being silently accepted and writing a blank handoff field. This is the correct behavior — a blank handoff field has no value.For invalid calls that omit a required field: now get a much more useful Zod message naming the field, marking it required, and showing how to satisfy the contract with a placeholder.
Test plan
npx tsc --noEmit)npm run build)axme_finalize_closecalls) continue to work — all six fields are already populated in those, and.min(1)accepts any non-empty string.Out of scope
🤖 Generated with Claude Code