Summary
ctx_note(action="write") can become unusable on tool-calling surfaces that require every declared property to be present. The model fills the action-irrelevant optional fields with schema-valid placeholder values (observed as note_id: 0 and note_ids: [1]), and Magic Context rejects the call before dispatching on action:
Error: 'note_id' and 'note_ids' cannot be used together; provide one or the other.
This is a Magic Context action-normalization issue exposed by a required-all provider/tool surface.
Environment
- Magic Context:
0.42.5
- OpenCode:
1.18.31
- Client: OpenCode TUI (CLI)
- Platform: Linux x64
- Bun used for repository verification:
1.4.2
Reproduction
Ask the agent to create a conditional note using only the documented write parameters:
{
"action": "write",
"content": "Re-run the integration test after the next release.",
"surface_condition": "When the latest release tag is newer than v1.0.0"
}
On the affected tool-calling surface, the actual generated tool payload repeatedly becomes:
{
"action": "write",
"content": "Re-run the integration test after the next release.",
"note_id": 0,
"note_ids": [1],
"surface_condition": "When the latest release tag is newer than v1.0.0"
}
The call then fails with:
Error: 'note_id' and 'note_ids' cannot be used together; provide one or the other.
Retrying with an explicit instruction to omit both fields does not help because the provider-visible tool surface requires all declared properties and the model must produce values for them.
Expected behavior
For action: "write", note_id and note_ids are irrelevant and should not prevent the note from being created.
The documented action contract is:
write: requires content; accepts optional surface_condition; uses neither ID field.
read: uses neither ID field.
update: requires note_id; does not use note_ids.
dismiss: accepts exactly one of note_id or note_ids.
Actual behavior
createCtxNoteTool() computes the action and immediately applies a global mutual-exclusion check before action-specific dispatch:
const action = args.action ?? (args.content?.trim() ? "write" : "read");
const hasNoteId = args.note_id !== undefined;
const hasNoteIds = args.note_ids !== undefined;
if (hasNoteId && hasNoteIds) {
return "Error: 'note_id' and 'note_ids' cannot be used together; provide one or the other.";
}
Location: packages/plugin/src/tools/ctx-note/tools.ts around lines 317–325.
As a result, fields that have no meaning for write are validated as though they were part of the selected action.
Root-cause evidence
- The Magic Context source correctly declares
note_id and note_ids with .optional() in ctxNoteArgsShape.
- A direct handler call containing only
action and content succeeds. Existing tools.test.ts coverage exercises this path.
- The OpenCode 1.18.31 plugin registry wraps plugin arguments with
z.object(args) and the resulting object JSON Schema keeps these fields out of required. A local same-instance and cross-Zod-instance conversion check produced no required array for the optional fields.
- The provider-facing tool surface in the affected session nevertheless requires every tool property. The model therefore emits schema-valid filler values. The values are generated to satisfy the visible schema; they are not inserted by
ctx_note.execute().
- The implementation already recognizes this provider behavior for another field:
// GPT-family models fill every optional param (content:"" for a read) ...
However, that compatibility handling currently covers action inference from empty content only. It does not normalize action-irrelevant ID fields.
Suggested fix
Normalize arguments according to the selected action before applying ID validation:
write / read: ignore note_id and note_ids.
update: use and validate note_id; ignore action-irrelevant note_ids filler.
dismiss: retain strict note_id versus note_ids mutual exclusion and all existing positive-ID/list-size checks.
This preserves strict validation where the IDs are semantically meaningful while making the multi-action tool robust on required-all tool protocols.
An alternative would be an action-discriminated provider schema, but the current OpenCode v1 plugin API exposes args as a raw Zod shape rather than a discriminated object union, so handler-side action normalization appears to be the narrowest compatible fix.
Regression coverage requested
Please add tests proving that:
write succeeds with note_id: 0 and note_ids: [1] present as irrelevant filler.
read succeeds with both filler fields present.
update succeeds with a valid note_id even if note_ids filler is present.
dismiss still rejects a request that intentionally supplies both meaningful forms.
dismiss still validates positive integer IDs and the 1–50 batch-size contract.
Verification already performed
bun test src/tools/ctx-note/tools.test.ts
25 pass, 0 fail
bun test src/plugin/tool-registry.test.ts src/tools/ctx-note/schema.test.ts
15 pass, 0 fail
These passing tests confirm the current source-level schema and normal direct-handler path, but they do not cover required-all provider filler values crossing into action-specific validation.
Summary
ctx_note(action="write")can become unusable on tool-calling surfaces that require every declared property to be present. The model fills the action-irrelevant optional fields with schema-valid placeholder values (observed asnote_id: 0andnote_ids: [1]), and Magic Context rejects the call before dispatching onaction:This is a Magic Context action-normalization issue exposed by a required-all provider/tool surface.
Environment
0.42.51.18.311.4.2Reproduction
Ask the agent to create a conditional note using only the documented write parameters:
{ "action": "write", "content": "Re-run the integration test after the next release.", "surface_condition": "When the latest release tag is newer than v1.0.0" }On the affected tool-calling surface, the actual generated tool payload repeatedly becomes:
{ "action": "write", "content": "Re-run the integration test after the next release.", "note_id": 0, "note_ids": [1], "surface_condition": "When the latest release tag is newer than v1.0.0" }The call then fails with:
Retrying with an explicit instruction to omit both fields does not help because the provider-visible tool surface requires all declared properties and the model must produce values for them.
Expected behavior
For
action: "write",note_idandnote_idsare irrelevant and should not prevent the note from being created.The documented action contract is:
write: requirescontent; accepts optionalsurface_condition; uses neither ID field.read: uses neither ID field.update: requiresnote_id; does not usenote_ids.dismiss: accepts exactly one ofnote_idornote_ids.Actual behavior
createCtxNoteTool()computes the action and immediately applies a global mutual-exclusion check before action-specific dispatch:Location:
packages/plugin/src/tools/ctx-note/tools.tsaround lines 317–325.As a result, fields that have no meaning for
writeare validated as though they were part of the selected action.Root-cause evidence
note_idandnote_idswith.optional()inctxNoteArgsShape.actionandcontentsucceeds. Existingtools.test.tscoverage exercises this path.z.object(args)and the resulting object JSON Schema keeps these fields out ofrequired. A local same-instance and cross-Zod-instance conversion check produced norequiredarray for the optional fields.ctx_note.execute().// GPT-family models fill every optional param (content:"" for a read) ...However, that compatibility handling currently covers action inference from empty
contentonly. It does not normalize action-irrelevant ID fields.Suggested fix
Normalize arguments according to the selected action before applying ID validation:
write/read: ignorenote_idandnote_ids.update: use and validatenote_id; ignore action-irrelevantnote_idsfiller.dismiss: retain strictnote_idversusnote_idsmutual exclusion and all existing positive-ID/list-size checks.This preserves strict validation where the IDs are semantically meaningful while making the multi-action tool robust on required-all tool protocols.
An alternative would be an action-discriminated provider schema, but the current OpenCode v1 plugin API exposes
argsas a raw Zod shape rather than a discriminated object union, so handler-side action normalization appears to be the narrowest compatible fix.Regression coverage requested
Please add tests proving that:
writesucceeds withnote_id: 0andnote_ids: [1]present as irrelevant filler.readsucceeds with both filler fields present.updatesucceeds with a validnote_ideven ifnote_idsfiller is present.dismissstill rejects a request that intentionally supplies both meaningful forms.dismissstill validates positive integer IDs and the 1–50 batch-size contract.Verification already performed
These passing tests confirm the current source-level schema and normal direct-handler path, but they do not cover required-all provider filler values crossing into action-specific validation.