Skip to content

ctx_note write fails when required-all tool schemas fill both ID fields #460

Description

@llc1123

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

  1. The Magic Context source correctly declares note_id and note_ids with .optional() in ctxNoteArgsShape.
  2. A direct handler call containing only action and content succeeds. Existing tools.test.ts coverage exercises this path.
  3. 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.
  4. 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().
  5. 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:

  1. write succeeds with note_id: 0 and note_ids: [1] present as irrelevant filler.
  2. read succeeds with both filler fields present.
  3. update succeeds with a valid note_id even if note_ids filler is present.
  4. dismiss still rejects a request that intentionally supplies both meaningful forms.
  5. 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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions