Skip to content

[BUG] OpenClaw schema tools misread response fields: valid notes render as undefined - invalid #1997

Closed

Description

@beru-ant-king

Bug Description

The OpenClaw plugin's schema_validate, schema_infer and schema_diff tools read response properties that the Basic Memory backend does not return. Consequently, valid notes are displayed as undefined - invalid, note-type labels are undefined or missing, and dropped fields are described using an undefined schema location.

This is reproducible with @basicmemory/openclaw-basic-memory@1.0.0 and Basic Memory 1.0.0. The affected renderers and schema client methods are unchanged on upstream main at 77f40181f35418e6792999751aa196d7d7e0b1b3 (checked October 30, 2026). The backend returns correct structured data; the defect is in the adapter's response interfaces and text rendering.

Steps To Reproduce

Live OpenClaw tool path

  1. Use the OpenClaw plugin 1.0.0 with Basic Memory 1.0.0 and an accessible local project containing a schema and at least one note which passes it.
  2. Call the plugin's schema_validate tool with that note's identifier and explicit project.
  3. Compare its rendered output with the backend result for the same note:
    bm tool schema validate --identifier '<existing-note>' --project '<project>' --local --json
  4. The backend reports passed: true with the note's identifier, while the plugin displays that note as undefined - invalid.

I executed this against an existing schema-covered note, not an empty/no-schema case. The project's name and note identifier are omitted here; the relevant result fields and all rendered output below retain their observed values.

Deterministic reproduction of all three renderers

Save the following as schema-rendering-repro.mjs, then run it against an already-installed plugin package directory (the directory containing its package.json and dist/):

node schema-rendering-repro.mjs /path/to/node_modules/@basicmemory/openclaw-basic-memory

This invokes the actual compiled registration functions and renderers, with synthetic response objects matching the backend's published response models. It does not start MCP, require credentials, or modify notes. I executed this exact script against the installed 1.0.0 package. The fixture route isolates rendering; it is separate from the live reproduction above.

import { resolve } from "node:path";
import { pathToFileURL } from "node:url";

const root = resolve(process.argv[2] ?? ".");
const note = (identifier, passed, errors = []) => ({
  note_identifier: identifier, schema_entity: "schema/adapter-audit", passed,
  field_results: [], unmatched_observations: {}, unmatched_relations: [],
  warnings: [], errors,
});
const fixtures = {
  schemaValidate: {
    note_type: "AdapterAudit", total_notes: 2, total_entities: 2,
    valid_count: 1, warning_count: 0, error_count: 1,
    results: [note("adapter-audit/valid", true),
      note("adapter-audit/invalid", false, ["Missing required field: fact"])],
    type_summaries: [],
  },
  schemaInfer: {
    note_type: "AdapterAudit", notes_analyzed: 2,
    field_frequencies: [{name: "fact", source: "observation", count: 2,
      total: 2, percentage: 1, sample_values: ["checked"],
      is_array: false, target_type: null}],
    suggested_schema: {fact: "string"}, suggested_required: ["fact"],
    suggested_optional: [], excluded: [],
  },
  schemaDiff: {
    note_type: "AdapterAudit", schema_found: true, new_fields: [],
    dropped_fields: [{name: "obsolete", source: "observation", count: 0,
      total: 2, percentage: 0}], cardinality_changes: [], notes_compared: 2,
  },
};
for (const [file, method, registration] of [
  ["schema-validate", "schemaValidate", "registerSchemaValidateTool"],
  ["schema-infer", "schemaInfer", "registerSchemaInferTool"],
  ["schema-diff", "schemaDiff", "registerSchemaDiffTool"],
]) {
  const module = await import(pathToFileURL(resolve(root, "dist/tools", file + ".js")));
  let tool;
  module[registration]({registerTool(t) { tool = t; }},
    {[method]: async () => fixtures[method]});
  const result = await tool.execute("schema-renderer-repro",
    {noteType: "AdapterAudit", project: "fixture"});
  console.log(`\n${file}:\n${result.content[0].text}`);
}

Expected Behavior

  • Validation identifies each note using note_identifier and displays its actual passed result. The passing fixture must be adapter-audit/valid - valid; the failing fixture must be adapter-audit/invalid - invalid, retaining its error.
  • Type headings use note_type for validation, inference and drift.
  • Drift output describes dropped fields using fields the backend supplies (name, source, count, total, percentage), without inventing a declared_in value.
  • Rendered per-note outcomes agree with aggregate counts and structured results.

Actual Behavior

Observed live tool output

**Notes:** 1 | **Valid:** 1 | **Warnings:** 0 | **Errors:** 0

- **undefined** — invalid

For the same note, the direct CLI JSON returned valid_count: 1, warning_count: 0, error_count: 0, and a result with a populated note_identifier, passed: true, warnings: [], and errors: [].

Output from the deterministic script

schema-validate:
**Notes:** 2 | **Valid:** 1 | **Warnings:** 0 | **Errors:** 1

- **undefined** — invalid
- **undefined** — invalid
  - error: Missing required field: fact

schema-infer:
**Type:** undefined
**Notes analyzed:** 2
**Required fields:** fact

### Field Frequencies
- **fact** — 100% (2 notes)

### Suggested Schema
```json
{
  "fact": "string"
}
```

schema-diff:
**Type:** undefined

### Dropped Fields (in schema, not in notes)
- **obsolete** — declared in undefined

Both validation fixtures are rendered as unnamed invalid notes, even though only one failed. Inference and drift also lose the type name. Drift's dropped-field description is declared in undefined.

Environment

  • OS: Ubuntu 26.04.1 LTS, x86_64.
  • Python: 3.14.10.
  • Basic Memory backend: 1.0.0, local stdio MCP and SQLite.
  • Backend installation: uv-managed basic-memory[litellm]==1.0.0.
  • OpenClaw plugin: @basicmemory/openclaw-basic-memory@1.0.0, enabled in the running host and installed through OpenClaw's npm-managed plugin installation.
  • OpenClaw: 2026.10.9, source build 5f6f15fed0d.
  • Node: v26.13.0.
  • Claude Desktop: not applicable; this is the OpenClaw integration.

Additional Context

Version and verification boundaries

  • npm latest was 1.0.0 when checked, published October 29, 2026 at 23:49 UTC.
  • I verified the registry tarball's integrity and compared its schema renderer and client source/compiled files with the installed package; those files match.
  • Upstream main's three schema renderer source files are byte-identical to the installed 1.0.0 files. The three schema client methods are unchanged too. Other client startup code has changed on main; I am not claiming the whole client is identical.
  • The installed backend response-model file matches main. Upstream main was source-inspected, not deployed and live-tested.
  • The live reproduction covers validation. The nonempty inference, dropped-field drift, and mixed valid/invalid cases were exercised through the actual compiled renderers using the synthetic backend-shaped fixtures above.

Impact and workaround

The text presented to the agent can contradict a successful validation result and conceal which note was checked. Drift output also loses useful evidence. The mismatch is deterministic for these nonempty response shapes; a zero-note or no-schema check would miss it.

Raw result details and direct CLI JSON preserve the backend's correct fields. Those are workarounds. This evidence does not indicate stored-note corruption or a need to reindex.

Related reports checked

I searched open/closed issues and PRs using the tool names, undefined, declared_in, note_identifier/passed, and schema-rendering terms. I found no report of this adapter field-mapping defect. In particular:

Possible Solution

Align the adapter's TypeScript response interfaces and consumers with the existing backend contract:

Area Adapter reads Backend supplies
Validation/inference/drift type entity_type note_type
Validation note identity identifier note_identifier
Validation outcome valid passed
Drift dropped-field description declared_in No such field; use source, count, total, percentage

The client casts the structured payload to the declared interfaces; it does not translate these fields, so the stale declarations hide the mismatch from TypeScript.

Pinned source references:

Regression coverage should include a passing note, a failing note, warning/error preservation, a nonempty inferred type, and at least one dropped field, using response objects derived from the backend contract. The existing aggregate counts should remain correct. A passing empty-result test would not cover this defect.

Activity

  1. added a commit that references this issue on Oct 30, 2026
    d7b1612
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions