Skip to content

OpenCode edit rejects valid edits[] calls with empty optional mode sentinels #171

Description

@excessivechaos

Description

The OpenCode edit boundary rejects a valid edits[] request when OpenCode or the model includes empty-string sentinels for the other optional mode fields.

Environment

  • @cortexkit/aft-opencode: 0.49.0
  • @cortexkit/aft-bridge: 0.49.0
  • OpenCode: 1.18.4
  • Windows x64

Reproduction

Send this to the hoisted edit tool:

{
  "filePath": "src/example.ts",
  "edits": [
    { "oldString": "old", "newString": "new" }
  ],
  "appendContent": "",
  "symbol": "",
  "content": ""
}

The same failure occurs if path is used instead of filePath.

Actual behavior

edit: conflicting modes: appendContent, edits, symbol/content

Expected behavior

Empty optional mode fields should be treated as omitted when exactly one real mode is present. The request should normalize to the equivalent of:

{
  "path": "src/example.ts",
  "edits": [
    { "oldString": "old", "newString": "new" }
  ]
}

Genuine mixed-mode calls should continue to be rejected. Empty content must remain valid when paired with a non-empty symbol, because replacing a symbol with empty content may be intentional.

Root cause

packages/aft-bridge/src/path-aliases.ts counts edit modes with property-presence checks:

if (hasOwn(record, "appendContent")) modes.push("appendContent");
if (hasOwn(record, "edits")) modes.push("edits");
if (hasOwn(record, "symbol") || hasOwn(record, "content")) modes.push("symbol/content");

Therefore empty-string sentinels are treated as active modes before the request reaches the Rust edit engine. The boundary should distinguish empty optional sentinels from meaningful mode values while preserving valid empty replacement content.

Workaround

A local tool.execute.before hook that removes empty competing mode fields allows the request through, but this should be fixed in the shared AFT boundary normalizer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions