{"record":{"id":"5cae9c672dd78a20","repo":"NousResearch/hermes-agent","slug":"content-required","errorCode":null,"errorMessage":"content required","messagePattern":"content required","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"acp_adapter/edit_approval.py","lineNumber":88,"sourceCode":"    return _EDIT_APPROVAL_REQUESTER.get()\n\n\ndef _read_text_if_exists(path: str) -> str | None:\n    p = Path(path).expanduser()\n    if not p.exists():\n        return None\n    if not p.is_file():\n        raise OSError(f\"Cannot edit non-file path: {path}\")\n    return p.read_text(encoding=\"utf-8\", errors=\"replace\")\n\n\ndef _proposal_for_write_file(arguments: dict[str, Any]) -> EditProposal:\n    path = str(arguments.get(\"path\") or \"\")\n    if not path:\n        raise ValueError(\"path required\")\n    content = arguments.get(\"content\")\n    if content is None:\n        raise ValueError(\"content required\")\n    return EditProposal(\n        tool_name=\"write_file\",\n        path=path,\n        old_text=_read_text_if_exists(path),\n        new_text=str(content),\n        arguments=dict(arguments),\n    )\n\n\ndef _proposal_for_patch_replace(arguments: dict[str, Any]) -> EditProposal:\n    path = str(arguments.get(\"path\") or \"\")\n    if not path:\n        raise ValueError(\"path required\")\n    old_string = arguments.get(\"old_string\")\n    new_string = arguments.get(\"new_string\")\n    if old_string is None or new_string is None:\n        raise ValueError(\"old_string and new_string required\")\n","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/acp_adapter/edit_approval.py#L70-L106","documentation":"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).","triggerScenarios":"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).","commonSituations":"Editor extension versions disagreeing on the write_file schema; LLM agents emitting {path, contents} instead of {path, content}; hand-rolled ACP clients.","solutions":["Send 'content' (string, possibly empty) in every write_file call.","Align the tool schema between the agent/client and the adapter — one canonical field name.","If the agent uses a different key, normalize arguments at the ACP boundary before proposal building.","Return the error to the client as invalid-params so the agent can retry with a correct payload."],"exampleFix":"# before\ncontent = arguments.get(\"content\")\nif content is None:\n    raise ValueError(\"content required\")\n\n# after — tolerate common aliases, still require presence\ncontent = arguments.get(\"content\", arguments.get(\"contents\"))\nif content is None:\n    raise ValueError(\"content required\")","handlingStrategy":"validation","validationCode":"if \"content\" not in arguments or arguments[\"content\"] is None:\n    return invalid_params(\"write_file requires 'content' (use '' for an empty file)\")","typeGuard":"def has_content(args: dict[str, Any]) -> bool:\n    return args.get(\"content\") is not None","tryCatchPattern":"try:\n    proposal = _proposal_for_write_file(arguments)\nexcept ValueError as err:\n    return {\"error\": {\"code\": -32602, \"message\": str(err)}}","preventionTips":["Document that content may be '' but must be present","Normalize alternative keys (contents/text) at the boundary","Add schema validation (jsonschema/pydantic) on incoming tool arguments"],"tags":["acp","editor-integration","validation","tool-schema","python"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}