HKUDS/DeepTutor · error · OpValidationError

edit: target_id {op.target_id} not found

Error message

edit: target_id {op.target_id} not found

What it means

OpValidationError raised in _validate when an EditOp's target_id is well-formed but doc.find(target_id) returns None — the entry being edited no longer (or never) exists in this document snapshot. Validation runs against the specific Document passed to ops.apply, so stale ids from an older version of the document are caught here, before any write.

Source

Thrown at deeptutor/services/memory/ops.py:89

    for op in ops:
        if isinstance(op, AddOp):
            if not op.text or len(op.text) > _MAX_TEXT_LEN:
                raise OpValidationError(
                    f"add: text length must be 1..{_MAX_TEXT_LEN} (got {len(op.text)})"
                )
            if not op.section or len(op.section) > _MAX_SECTION_LEN:
                raise OpValidationError(f"add: invalid section {op.section!r}")
            if not op.refs:
                raise OpValidationError("add: refs must be non-empty")
            for ref in op.refs:
                if not is_valid_ref(ref):
                    raise OpValidationError(f"add: malformed ref {ref!r}")
        elif isinstance(op, EditOp):
            if not is_entry_id(op.target_id):
                raise OpValidationError(f"edit: malformed target_id {op.target_id!r}")
            if doc.find(op.target_id) is None:
                raise OpValidationError(f"edit: target_id {op.target_id} not found")
            if not op.new_text or len(op.new_text) > _MAX_TEXT_LEN:
                raise OpValidationError(
                    f"edit: text length must be 1..{_MAX_TEXT_LEN} (got {len(op.new_text)})"
                )
            if not op.new_refs:
                raise OpValidationError("edit: refs must be non-empty")
            for ref in op.new_refs:
                if not is_valid_ref(ref):
                    raise OpValidationError(f"edit: malformed ref {ref!r}")
            if op.target_id in deletes:
                raise OpValidationError(
                    f"batch conflict: edit and delete on same id {op.target_id}"
                )
            edits.add(op.target_id)
        elif isinstance(op, DeleteOp):
            if not is_entry_id(op.target_id):
                raise OpValidationError(f"delete: malformed target_id {op.target_id!r}")
            if doc.find(op.target_id) is None:

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Re-read the document and re-resolve the entry id immediately before applying the edit; retry with the fresh id if the entry still exists
  2. If the entry is gone, convert the edit into an AddOp of the new text instead of failing
  3. Serialize memory writes (or use the run manager) so consolidation can't remove entries between read and apply

Example fix

# before
op = EditOp(target_id=stale_id, new_text=new_note, new_refs=[ref])
result = ops.apply(old_doc, [op])

# after
doc = load_doc()  # fresh snapshot
if doc.find(stale_id) is None:
    op = AddOp(text=new_note, section=last_known_section, refs=[ref])
else:
    op = EditOp(target_id=stale_id, new_text=new_note, new_refs=[ref])
result = ops.apply(doc, [op])
Defensive patterns

Strategy: retry

Validate before calling

doc = load_doc()  # fresh snapshot
if doc.find(target_id) is None:
    target_id = reresolve_entry(doc, old_text_matcher)
    if target_id is None:
        op = AddOp(text=new_text, section=section, refs=new_refs)

Type guard

def target_exists(doc, target_id: str) -> bool:
    return doc.find(target_id) is not None

Try / catch

try:
    ops.apply(doc, [op])
except OpValidationError as e:
    if "not found" in str(e):
        doc = load_doc()
        if doc.find(op.target_id) is not None:
            ops.apply(doc, [op])  # retry on fresh snapshot
        else:
            ops.apply(doc, [to_add_op(op)])  # degrade to add
    else:
        raise

Prevention

When it happens

Trigger: Editing based on a stale document: the entry was deleted or consolidated away after the caller read the doc, so the id isn't in the current Document passed to apply. Also passing the wrong Document (different KB/slot) to apply.

Common situations: Read-modify-write races with the consolidator or another session; long-lived cached entry lists in an agent loop; LLM editing an entry id from an earlier turn after memory changed; tests reusing fixtures across mutations.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/93347660b73b5aaa. Report an issue: GitHub.