langchain-ai/deepagents · error · ValueError

DiffStats counts cannot be negative, got additions={self.add

Error message

DiffStats counts cannot be negative, got additions={self.additions}, deletions={self.deletions}

What it means

`DiffStats.__post_init__` validates that `additions` and `deletions` are non-negative. A negative count means the caller computed diff statistics incorrectly (e.g. mis-parsed hunk headers like `@@ -1,3 +1,-2 @@` or subtracted counts in the wrong order). The library throws immediately rather than propagating nonsense metrics into UI or reporting code.

Source

Thrown at libs/code/deepagents_code/diff_utils.py:91

    additions: int
    deletions: int

    def __post_init__(self) -> None:
        """Reject negative counts.

        Both in-repo producers derive from `difflib`, so this only guards direct
        construction — but the type is public and reaches a delete prompt, where
        the number gates destroying a file.

        Raises:
            ValueError: If either count is negative.
        """
        if self.additions < 0 or self.deletions < 0:
            msg = (
                f"DiffStats counts cannot be negative, got additions="
                f"{self.additions}, deletions={self.deletions}"
            )
            raise ValueError(msg)


def split_diff_lines(diff: str) -> list[str]:
    r"""Split a unified diff back into the lines it was assembled from.

    Deliberately not `splitlines()`. Every diff reaching this function is
    `"\n"`-joined from lines that themselves came from `splitlines()` or
    `split("\n")`, so no element can contain a line boundary and `"\n"` is the
    exact inverse. `splitlines()` also breaks on `\r`, `\v`, `\f`, U+2028,
    U+2029 and U+0085, which splits a single diff line into fragments. The tail
    fragment carries no `+`/`-` marker, so it would render as an unmarked note —
    on the approval prompt that means changed content shown as neutral metadata.

    Check any new producer against that invariant rather than against a list of
    the current ones.

    Args:
        diff: Unified diff string.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Fix the computing code so counts are clamped to >= 0 (e.g. `max(0, parsed_count)`).
  2. Validate the diff text before parsing: hunk headers must match `@@ -a,b +c,d @@` with non-negative values.
  3. Log the raw diff alongside the error to locate the malformed hunk that produced the negative number.

Example fix

// before
stats = DiffStats(additions=old_count - new_count, deletions=0)
// after
stats = DiffStats(additions=max(0, new_count - old_count), deletions=max(0, old_count - new_count))
Defensive patterns

Strategy: validation

Validate before calling

def make_diff_stats(additions: int, deletions: int) -> DiffStats:
    if additions < 0 or deletions < 0:
        raise ValueError(f"bad diff counts: +{additions} -{deletions}")
    return DiffStats(additions=additions, deletions=deletions)

Type guard

def has_valid_diff_counts(s: object) -> bool:
    return isinstance(s, DiffStats) and s.additions >= 0 and s.deletions >= 0

Try / catch

try:
    stats = DiffStats(additions=add, deletions=dele)
except ValueError as exc:
    logger.error("Diff parsing produced invalid counts: %s", exc)
    stats = DiffStats(additions=0, deletions=0)

Prevention

When it happens

Trigger: Constructing `DiffStats(additions=..., deletions=...)` with either field negative — usually counts derived from malformed diff hunk headers or by subtracting a larger value from a smaller one.

Common situations: Parsing truncated or hand-edited unified diffs; a hunk-header regex capturing a stray minus sign; off-by-one arithmetic when computing net changes; feeding a custom diff format the parser was not designed for.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/858c257446cfdfad. Report an issue: GitHub.