NousResearch/hermes-agent · error · ValueError

content required

Error message

content required

What it means

Raised by _proposal_for_write_file when the tool-call arguments lack 'content' (it is None). write_file's whole purpose is to write content, and the approval diff is built from old_text vs the new content — without content there is no proposal to approve. Note the check is `is None`, so an empty string is accepted intentionally (writing an empty file is legal).

Source

Thrown at acp_adapter/edit_approval.py:88

    return _EDIT_APPROVAL_REQUESTER.get()


def _read_text_if_exists(path: str) -> str | None:
    p = Path(path).expanduser()
    if not p.exists():
        return None
    if not p.is_file():
        raise OSError(f"Cannot edit non-file path: {path}")
    return p.read_text(encoding="utf-8", errors="replace")


def _proposal_for_write_file(arguments: dict[str, Any]) -> EditProposal:
    path = str(arguments.get("path") or "")
    if not path:
        raise ValueError("path required")
    content = arguments.get("content")
    if content is None:
        raise ValueError("content required")
    return EditProposal(
        tool_name="write_file",
        path=path,
        old_text=_read_text_if_exists(path),
        new_text=str(content),
        arguments=dict(arguments),
    )


def _proposal_for_patch_replace(arguments: dict[str, Any]) -> EditProposal:
    path = str(arguments.get("path") or "")
    if not path:
        raise ValueError("path required")
    old_string = arguments.get("old_string")
    new_string = arguments.get("new_string")
    if old_string is None or new_string is None:
        raise ValueError("old_string and new_string required")

View on GitHub (pinned to c896c09c42)

Solutions

  1. Send 'content' (string, possibly empty) in every write_file call.
  2. Align the tool schema between the agent/client and the adapter — one canonical field name.
  3. If the agent uses a different key, normalize arguments at the ACP boundary before proposal building.
  4. Return the error to the client as invalid-params so the agent can retry with a correct payload.

Example fix

# before
content = arguments.get("content")
if content is None:
    raise ValueError("content required")

# after — tolerate common aliases, still require presence
content = arguments.get("content", arguments.get("contents"))
if content is None:
    raise ValueError("content required")
Defensive patterns

Strategy: validation

Validate before calling

if "content" not in arguments or arguments["content"] is None:
    return invalid_params("write_file requires 'content' (use '' for an empty file)")

Type guard

def has_content(args: dict[str, Any]) -> bool:
    return args.get("content") is not None

Try / catch

try:
    proposal = _proposal_for_write_file(arguments)
except ValueError as err:
    return {"error": {"code": -32602, "message": str(err)}}

Prevention

When it happens

Trigger: An ACP write_file call whose JSON arguments omit 'content' or pass null — e.g. an agent that streams content separately and forgot the field, or a schema/client mismatch where the field is named differently (text/contents).

Common situations: Editor extension versions disagreeing on the write_file schema; LLM agents emitting {path, contents} instead of {path, content}; hand-rolled ACP clients.

Related errors


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