Skip to content

Add structured ApplyFileDiffsResult.Failure types (QUALITY-1264) - #351

Open
warp-agent-staging[bot] wants to merge 1 commit into
mainfrom
varoon/structured-apply-diff-errors
Open

Add structured ApplyFileDiffsResult.Failure types (QUALITY-1264)#351
warp-agent-staging[bot] wants to merge 1 commit into
mainfrom
varoon/structured-apply-diff-errors

Conversation

@warp-agent-staging

@warp-agent-staging warp-agent-staging Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Add structured Failure types to ApplyFileDiffsResult.Error in task.proto, enabling server-controlled wording of edit_files failures.

Part of QUALITY-1264 — "Structured apply-diff errors: server-controlled edit_files failure messaging." This is the proto contract PR (Agent P), defining the wire format that the client (Agent C) and server (Agent S) will implement against.

What changed

Extended ApplyFileDiffsResult.Error to carry structured, typed failure data alongside the existing back-compat message string:

message Error {
  string message = 1 [(sensitive) = true];  // back-compat / Opaque carrier
  repeated Failure failures = 2;            // structured failures, new
}

Added Failure message with oneof kind covering all 11 failure categories:

  • UnmatchedDiffs — file + fuzzy_match_failure_count + per-block SearchBlockFailure details (search text, truncated flag, expected line range)
  • ChangesAlreadyApplied, MissingFile, ReadFailed, AlreadyExists, MultipleFileCreation, MultipleFileRenames, MutatedDeletedFile — each carries the file path
  • no_diffs_applicable, remote_file_operations_unsupportedgoogle.protobuf.Empty
  • Opaque — prerendered message for legacy/uncategorized failures

All user-content string fields are marked (sensitive) = true (PRODUCT invariant 7).

Validation

This is a pure schema/contract change — no behavioral test exists.

  • Testing-exempt rationale: schema-only change; no executable behavior to test.
  • (sensitive) audit: every user-content string field is marked (sensitive) = true: Error.message, UnmatchedDiffs.file, SearchBlockFailure.search, all per-file variant .file fields, and Opaque.message.
  • Go build: go build ./... passes on the generated bindings.
  • Regeneration in sync: ./script/generate -a multi_agent -v v1 produces no meaningful diff (only a protoc version comment change, which CI ignores).
  • Rust: generated at compile time via build.rs; no artifacts to commit.

Compatibility

The message field (field 1) is preserved for back-compat:

  • Old client + new server: server reads message → identical behavior to today
  • New client + old server: server ignores unknown failures field, reads message → identical behavior
  • New client + new server: server renders from failures, ignores message (QUALITY-1264 goal)

Originating thread

Slack thread

Conversation: https://staging.warp.dev/conversation/c113b5ca-c1de-4d63-a518-3b4f8d80e113
Run: https://oz.staging.warp.dev/runs/019fa5f3-f83d-788f-a65f-7bf202c66ddd

This PR was generated with Oz.

Extend ApplyFileDiffsResult.Error to carry structured failures while
keeping the 'message' field for backward compatibility.

Add a new Failure oneof with variants: UnmatchedDiffs (with
SearchBlockFailure per-block details), ChangesAlreadyApplied,
MissingFile, ReadFailed, AlreadyExists, MultipleFileCreation,
MultipleFileRenames, MutatedDeletedFile, no_diffs_applicable,
remote_file_operations_unsupported, and Opaque.

All user-content string fields are marked (sensitive).

Regenerate Go bindings.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Jul 27, 2026
@warp-agent-staging
warp-agent-staging Bot marked this pull request as ready for review July 27, 2026 23:52
@warp-agent-staging
warp-agent-staging Bot requested a review from a team as a code owner July 27, 2026 23:52
@warp-agent-staging
warp-agent-staging Bot requested review from danielpeng2 and vkodithala and removed request for danielpeng2 July 27, 2026 23:52

@warp-agent-staging warp-agent-staging Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

Extends ApplyFileDiffsResult.Error with repeated Failure failures = 2 alongside the preserved back-compat string message = 1, and adds a Failure message whose oneof kind covers all 11 categories from PRODUCT.md with nested UnmatchedDiffs / SearchBlockFailure / per-path types. The contract is wire-safe, matches the approved TECH.md snippet almost verbatim, and the checked-in Go bindings are in sync; the only findings are documentation-level.

Concerns

The expected_start_line / expected_end_line sentinel deserves a stronger comment, flagged inline. Because this file is edition = "2023", plain uint32 still carries explicit presence, so the generated Go bindings expose HasExpectedStartLine() / ClearExpectedStartLine() — while the Rust bindings are produced by rewriting edition = "2023" to syntax = "proto3" in apis/multi_agent/v1/gen/rust/build.rs, which drops scalar presence entirely. Presence is therefore only expressible on the Go side, 0 is the only portable "unknown" signal, and a Go consumer reaching for the generated Has*() accessor would be building on a contract the Rust client cannot honor. Both current consumers do the right thing (client encodes 0, server checks > 0), so this is a future-proofing ask rather than a live bug.

Relatedly, the PR description does not mention that this ships a 0 sentinel where TECH.md specified optional uint32. The deviation is justified — the optional label is not valid under protobuf editions, so the spec's literal form would not compile — but this is precisely the cross-repo agreement point most likely to drift later, and it should be called out in the description rather than left for a reader to reconstruct.

Regeneration rewrote the recorded protoc version from v7.35.1 back to v5.29.3 across 15 otherwise-untouched .pb.go files. CI ignores these lines via its git diff -I '^[[:space:]]*//.*(protoc|protoc-gen-go)[[:space:]]*v[0-9]' filter so nothing breaks, but it moves the repo's recorded toolchain backwards and the next contributor regenerating on a current brew protobuf will flip all 15 lines again. Reverting those version-comment lines before merge would keep the diff to the one file that actually changed.

Verdict

Found: 0 critical, 0 important, 3 suggestions

Checked directly: gh pr checks reports 6/6 green including Check Generated Code; regenerating locally with protoc 30.2 and applying CI's exact -I filter yields zero meaningful drift, confirming the committed bindings match the .proto; go build ./... and go vet ./... pass in apis/multi_agent. Field message = 1 is preserved byte-for-byte and no existing tag in ApplyFileDiffsResult is reused or renumbered, so all four PRODUCT compatibility criteria (10-13) hold on the wire. (sensitive) = true is present on every user-content string — Error.message, UnmatchedDiffs.file, SearchBlockFailure.search, all seven per-path file fields, and Opaque.message — satisfying PRODUCT invariant 7, and SearchBlockFailure.truncated satisfies invariant 8. Testing-exempt as a pure schema change; the PR body names the category and states the rationale, which is the correct treatment here.

Approve (posted as a comment review: GitHub rejects APPROVE because this PR was opened by the same factory app identity that is reviewing it — the verdict is accepted, a human still merges.)

Review run

https://oz.staging.warp.dev/runs/019fa63c-760c-749f-84ae-94e588e768af

Comment on lines +1373 to +1378
// 1-indexed inclusive start line the block was expected to match.
// 0 means unknown / not applicable.
uint32 expected_start_line = 3;
// 1-indexed inclusive end line the block was expected to match.
// 0 means unknown / not applicable.
uint32 expected_end_line = 4;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 [SUGGESTION] This file is edition = "2023", where plain uint32 still has explicit presence — the generated Go bindings expose HasExpectedStartLine()/ClearExpectedStartLine(). The Rust bindings, however, are produced by rewriting edition = "2023" to syntax = "proto3" in apis/multi_agent/v1/gen/rust/build.rs, which drops scalar presence, so the Rust client cannot distinguish unset from 0. Presence is thus only expressible on one side of the wire and 0 is the only portable unknown signal. Both consumers currently agree (client encodes 0, server checks > 0), but nothing in the comment stops a future Go consumer from switching to Has*() and silently diverging. Say so explicitly here.

Suggested change
// 1-indexed inclusive start line the block was expected to match.
// 0 means unknown / not applicable.
uint32 expected_start_line = 3;
// 1-indexed inclusive end line the block was expected to match.
// 0 means unknown / not applicable.
uint32 expected_end_line = 4;
// 1-indexed inclusive start line the block was expected to match.
// 0 means unknown / not applicable.
//
// Use 0 -- not field presence -- as the "unknown" signal. This file is
// edition 2023, so the Go bindings expose HasExpectedStartLine(), but
// the Rust bindings are generated by rewriting the edition to
// syntax = "proto3" (gen/rust/build.rs), which drops scalar presence.
// Presence is therefore not portable across consumers; 0 is.
uint32 expected_start_line = 3;
// 1-indexed inclusive end line the block was expected to match.
// 0 means unknown / not applicable. See expected_start_line above.
uint32 expected_end_line = 4;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant