Skip to content

Content digests buffer entire git diff --binary patches in memory #249

Description

@rohanpoudel2

Summary

The content digest buffers a whole git diff --binary patch in memory before hashing it. A repository containing a large changed binary can therefore exhaust the workbench process during setup inspection, before any review begins.

Correction to an earlier version of this issue: I first wrote that both content-digest functions do this. On main there is only one git diff --binary call site, in worktree_content_digest_for_context. A committed base..head equivalent exists only in the unmerged PR #241, so if that lands it should adopt the same streaming helper.

Environment

  • @openai/codex-security 0.1.5, current main at ea19f24
  • macOS 15 (Darwin 25.5.0), Python 3.14.5

Where it is

sdk/typescript/_bundled_plugin/scripts/workbench_target.pyworktree_content_digest_for_context obtains the tracked diff through git_bytes, which is subprocess.run(..., capture_output=True), so completed.stdout holds the entire patch. The digest is then computed from that single value.

Binary patches are larger than the underlying files: measured on a 20 MiB incompressible file, git diff --binary produced a 27 MB patch, about 1.29x.

Measured peak RSS in the Python process on that fixture: 145.8 MiB, against a 25.2 MiB floor for a clean worktree. Git's own peak was 165.6 MiB, which is inherent to producing the patch and not affected by this issue.

This runs on every setup inspection, so it is not confined to scan registration.

Why this is not a one-line change

update_digest_field frames each value with an 8-byte big-endian length prefix, so the total byte count must be known before any content is hashed. Streaming therefore needs either a second full git diff pass to learn the length — non-atomic, and double the work — or spooling the patch to a temporary file and hashing from it.

Whichever shape is chosen, the resulting digest must stay byte-identical to today's, because recorded digests are compared against freshly computed ones when a saved selection is revalidated.

Suggested direction

Add a streaming helper next to git_bytes that spools stdout to a temporary file, then feeds the framed length and the file contents into the digest. Apply it to both digest functions rather than only one, since the working-tree path dominates. A fixture asserting old and new produce the same digest for the same input would pin the compatibility requirement.

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