NousResearch/hermes-agent · error · ValueError

patch content required

Error message

patch content required

What it means

Raised by _proposal_for_patch_v4a in the ACP adapter when handling V4A protocol patch calls: the arguments' 'patch' field must be a non-empty string containing the diff payload used both to extract affected file paths and to show the user the exact change for approval. A missing, non-string, or empty patch field is rejected before parsing. This is a payload-shape error from the ACP client, not a filesystem or diff-application error.

Source

Thrown at acp_adapter/edit_approval.py:158

            paths.append(path)
    for match in re.finditer(
        r'^\*\*\*\s+Move\s+File:\s*(.+?)\s*->\s*(.+)$',
        patch_body,
        re.MULTILINE,
    ):
        src = match.group(1).strip()
        dst = match.group(2).strip()
        if src:
            paths.append(src)
        if dst:
            paths.append(dst)
    return paths


def _proposal_for_patch_v4a(arguments: dict[str, Any]) -> EditProposal:
    patch_body = arguments.get("patch")
    if not isinstance(patch_body, str) or not patch_body:
        raise ValueError("patch content required")

    paths = _extract_v4a_patch_paths(patch_body)
    if not paths:
        raise ValueError("no file paths found in V4A patch")

    proposal_path = paths[0] if len(paths) == 1 else ", ".join(paths)
    old_text = _read_text_if_exists(paths[0]) if len(paths) == 1 else None
    return EditProposal(
        tool_name="patch",
        path=proposal_path,
        old_text=old_text,
        # ACP only supports a single diff payload here.  Surface the exact V4A
        # patch content before execution so patch-mode calls are permissioned
        # and denied patches cannot mutate.
        new_text=patch_body,
        arguments=dict(arguments),
    )

View on GitHub (pinned to c896c09c42)

Solutions

  1. Send the full unified diff as a single string under arguments.patch.
  2. Align the ACP client/extension and adapter versions so the patch payload schema matches.
  3. If hunks arrive structured, serialize them to a unified diff string before dispatch.
  4. Catch ValueError here and return an invalid-params response so the client can retry correctly.

Example fix

# before
patch_body = arguments.get("patch")
if not isinstance(patch_body, str) or not patch_body:
    raise ValueError("patch content required")

# after — accept a structured hunks list by serializing it first
raw = arguments.get("patch")
if isinstance(raw, list):
    raw = "\n".join(str(hunk) for hunk in raw)
if not isinstance(raw, str) or not raw:
    raise ValueError("patch content required")
patch_body = raw
Defensive patterns

Strategy: validation

Validate before calling

patch_body = arguments.get("patch")
if not isinstance(patch_body, str) or not patch_body.strip():
    return invalid_params("patch call requires 'patch' as a non-empty unified-diff string")

Type guard

def is_patch_payload(v: Any) -> bool:
    return isinstance(v, str) and bool(v.strip())

Try / catch

try:
    proposal = _proposal_for_patch_v4a(arguments)
except ValueError as err:
    return {"error": {"code": -32602, "message": str(err)}}  # client retries with a proper diff

Prevention

When it happens

Trigger: An ACP client sends a patch-mode call with patch omitted, null, an empty string, or a non-string (e.g. a list of hunks or an object) — commonly a protocol version mismatch where the client sends structured hunks while the adapter expects a single diff string.

Common situations: Editor extension upgrade changing the patch payload shape; agents splitting patches into multiple calls but sending the structure field empty; serialization bugs dropping large diff bodies.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/babdfd469a08e17b. Report an issue: GitHub.