Skip to content

Commit da5f3d6

Browse files
directive: image-guard pipeline (#87) (#88)
* directive: image-guard pipeline (#87) Replaces v3.2.1's static CACHE_FIX_IMAGE_MAX_DIM with a conditional three-pass pipeline matching Anthropic's documented rules: Pass 1 — conditional dim cap (count >20 → 2000px, else 8000px) Pass 2 — request-size guard (Buffer.byteLength after mutations, default 30MB budget vs 32MB API ceiling) Pass 3 — optional Lanczos resize via lazy-loaded sharp peer-dep, opt-in via CACHE_FIX_IMAGE_PRESERVE_DETAIL=1, preserves original media type (no transcoding in v1) Plus hard image-count cap (100 for 200K-window models incl. claude-3-7-sonnet-, 600 for others; unknown model → safer 100). Activation: prefix-diff pattern (enabled: true + runtime env-gate), matching overage-warning and upstream-change-detection. v3.2.1 legacy KEEP_LAST and MAX_DIM gates remain functional for back-compat. Includes the two Codex design-stage reviews (REQUEST CHANGES → APPROVED with notes) so the audit trail is in-tree. Ref #87 * docs: add PR 88 codex directive review * directive: address Codex's two blockers on PR #88 review 1. Pin Pass 3's trigger and target unambiguously. Old wording let Pass 3 read either as "resize when Pass 1 would have stripped" (target = rejection cap) or as "quality resize whenever above native cap" (target = native cap). These produce different behavior for the case Codex called out: a 5000×5000 image in a 5-image request with PRESERVE_DETAIL=1. Resolved: passes are independent. Execution order is now explicitly Pass 0 → Pass 3 → Pass 1 → Pass 2 → count cap. Pass 3 (when enabled) targets the native cap (2576 for claude-opus-4-7, else 1568) for ANY image above it, regardless of Pass 1's outcome. Pass 1 strips only — never resizes. Pass 3 resizes only — never strips. Added test 16 specifically for the above-native, below-rejection gap. 2. Drop the dead 600-cap branch. Old spec: 100 for 200K-window models, 600 for "other models." Classifier collapsed every current CC-pipeline model to 100, so nothing took the 600 branch — CACHE_FIX_IMAGE_COUNT_MAX_OTHER was unreachable spec surface. Resolved: single CACHE_FIX_IMAGE_COUNT_MAX env var, default 100. Users who genuinely need 600 (older Claude 1/2/Instant models) can set it explicitly. Dropped the 200K prefix list, dropped the model_classification_unknown_count telemetry counter. Pass 3 keeps a one-line claude-opus-4-7 prefix check for native cap selection. Also addressed Codex's two non-blocking notes: - Removed the unfounded "server's default Lanczos" claim (server-side algorithm is not documented; the directive only establishes that it's blind from the client's perspective). - Tightened the package.json rationale to focus on "no install requirement" rather than "prevents npm warnings." Ref #87 * Add Codex rereview for PR 88 directive * feat: implement image-guard pipeline (#87) per directive v3.3.0 image-guard pipeline. Replaces v3.2.1's static MAX_DIM with a conditional pipeline that mirrors Anthropic's actual rules: - Pass 0 (back-compat): KEEP_LAST tool_result strip - Pass 3 (opt-in via PRESERVE_DETAIL=1): Lanczos resize via sharp, preserves aspect ratio and media type, native cap 2576px Opus 4.7 or 1568px otherwise - Pass 1: conditional rejection-cap strip (2000px when count > 20, 8000px otherwise; MAX_DIM overrides) - Pass 2: request-size guard (default 30 MB budget, 2 MB headroom from Anthropic's 32 MB ceiling) - Hard image-count cap (default 100) Execution order: Pass 0 → Pass 3 → Pass 1 → Pass 2 → count cap. Each pass is independent — Pass 1 never resizes; Pass 3 never strips. Activation: - Pipeline gated by CACHE_FIX_IMAGE_GUARD=1 (top-level) - Pass 3 additionally gated by CACHE_FIX_IMAGE_PRESERVE_DETAIL=1 - Legacy KEEP_LAST and MAX_DIM continue to work standalone for v3.2.1 users — no breaking changes - Extension activation pattern: enabled:true in extensions.json, runtime env-var gates inside (matches overage-warning shape) `sharp` is declared only in peerDependenciesMeta as optional. The proxy never imports it at module load; runtime `await import("sharp")` in Pass 3 handles MODULE_NOT_FOUND cleanly. Without sharp, Pass 3 sets library_missing telemetry and skips; Pass 1 + Pass 2 still run. Telemetry: ctx.meta.imageGuardStats carries the full counter set (counts + bytes + estimated tokens + library_missing flag). One stderr line per processed request when the pipeline did anything observable. New files: - proxy/image-resize.mjs — lazy sharp wrapper - test/proxy-image-guard.test.mjs — 42 tests covering directive's test plan (activation, every Pass, count cap, every precedence matrix row, telemetry shape, sharp-unavailable + sharp-throws fallbacks). Sharp tests use injected fake — no real install needed. Modified: - proxy/extensions/image-strip.mjs — extended; legacy paths unchanged - proxy/extensions.json — register image-strip at order 150 - package.json — version 3.3.0; sharp peerDependenciesMeta optional - README.md — pipeline section + precedence matrix + tunables table - docs/extension-impact-guide.md — extension #9 image-strip section Tests: 595/595 pass (42 new + 553 existing). All v3.2.1 legacy paths unchanged; back-compat regression coverage explicit. * Add Codex implementation review for PR 88 * fix: address Codex implementation review blockers (#88) Codex review found 2 blockers + 1 telemetry-drift note. All addressed: 1. README precedence matrix is now verbatim from the directive. Ten rows restored to the directive's exact wording instead of paraphrased shorthand. The matrix shipped in README.md must match docs/directives/proxy-image-guard-pipeline.md verbatim per the directive's reviewer checklist. 2. Pass 1-only mutations now emit the [image-guard] stderr summary. Added images_stripped_pass1 counter to imageGuardStats, increment it in runPass1RejectionCapStrip on each strip, and include it in the onRequest didSomething check + summary line. Reproducer from Codex's review (single 9000x9000 PNG, IMAGE_GUARD=1, sonnet model) now emits "[image-guard] stripped=1 ..." correctly. 3. (Note, not blocker) request_bytes_after now recomputed after the count-cap pass. Without this, count-cap-only requests reported pre-trim byte totals — telemetry drift, not enforcement drift, but misleading on T20/T21-style requests. runImageCountCap re-measures the body after evictions and updates request_bytes_after / request_bytes_headroom. New tests: - T34a: Pass 1-only stripping emits stderr summary - T34b: count-cap-only request reports updated request_bytes_after Tests: 597/597 pass (2 new + 595 existing). * Add Codex rereview artifact for PR 88 implementation Reconstructs vsits-codex-review-agent's rereview verdict from its PR comment because the bot's push to feature/image-guard-pipeline was blocked with 403 (recurring permission gap on this App's installation). The verdict text matches the bot's PR comment dated 2026-04-30T13:32:01Z verbatim. The bot's local commit was 775666f; that commit could not be pushed, so committing here as AI Team Lead. See PR #88 comment thread for the original verdict author signature. --------- Co-authored-by: vsits-proxy-builder[bot] <279815601+vsits-proxy-builder[bot]@users.noreply.github.com>
1 parent cba8d36 commit da5f3d6

14 files changed

Lines changed: 2328 additions & 41 deletions

README.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ export CACHE_FIX_IMAGE_KEEP_LAST=3
334334

335335
Keeps images in the last 3 user messages, replaces older ones with a text placeholder. Only targets `tool_result` blocks — user-pasted images are never touched.
336336

337-
### Oversized-image guard
337+
### Oversized-image guard (legacy, v3.2.1)
338338

339339
```bash
340340
export CACHE_FIX_IMAGE_MAX_DIM=2000
@@ -355,6 +355,60 @@ The two compose: with both set, `KEEP_LAST` runs first (drops the count), then `
355355

356356
Pure-JS PNG and JPEG header parsing — no native deps. Other formats (GIF, WebP, AVIF, BMP) pass through unchanged regardless of dimension. Fail-open: images whose dimensions can't be parsed (truncated header, unsupported format) are kept rather than stripped — better to send a request that might error than to strip a valid image we just couldn't measure.
357357

358+
### Image-guard pipeline (v3.3.0)
359+
360+
A conditional pipeline that mirrors Anthropic's actual rules. Strictly opt-in via a single env var:
361+
362+
```bash
363+
export CACHE_FIX_IMAGE_GUARD=1
364+
```
365+
366+
When enabled, the proxy runs:
367+
368+
| Pass | Trigger | Action |
369+
|------|---------|--------|
370+
| **Pass 0** (legacy) | `CACHE_FIX_IMAGE_KEEP_LAST=N` set | Strip tool_result images from user messages older than N most recent |
371+
| **Pass 3** | `CACHE_FIX_IMAGE_PRESERVE_DETAIL=1` AND image long edge > model native cap | Lanczos resize via `sharp` to native cap (2576 px for Opus 4.7, 1568 px otherwise), preserve aspect ratio and media type |
372+
| **Pass 1** | image long edge > active rejection cap | Strip and replace with forensic placeholder. Active cap = `MAX_DIM` if set, else 2000 px (when count > 20) or 8000 px (count ≤ 20) |
373+
| **Pass 2** | request body exceeds `CACHE_FIX_IMAGE_REQUEST_SIZE_MAX` (default 30 MB) | Drop oldest images until under budget |
374+
| **Count cap** | surviving image count > `CACHE_FIX_IMAGE_COUNT_MAX` (default 100) | Drop oldest images down to the cap |
375+
376+
Execution order: **Pass 0 → Pass 3 → Pass 1 → Pass 2 → count cap**. Each pass is independent — Pass 1 never resizes; Pass 3 never strips.
377+
378+
#### Optional `sharp` dependency
379+
380+
Pass 3 requires [sharp](https://www.npmjs.com/package/sharp) for Lanczos resize. It's declared as an **optional peer dependency** — install separately if you want Pass 3:
381+
382+
```bash
383+
npm install sharp
384+
```
385+
386+
If `sharp` is missing, Pass 3 skips cleanly (telemetry records `library_missing: true`); Pass 1 + Pass 2 + the count cap still run.
387+
388+
#### Precedence matrix
389+
390+
| Env var combination | Behavior |
391+
|---|---|
392+
| Nothing set | No image processing (back-compat default; the extension short-circuits). |
393+
| `KEEP_LAST=N` only | Existing v3.2.1: count cap on tool_result images in user messages, runs first. No pipeline. |
394+
| `MAX_DIM=N` only | Existing v3.2.1: hard size cap, strip-only. No pipeline. |
395+
| `KEEP_LAST=N` + `MAX_DIM=N` | Existing v3.2.1 composition: `KEEP_LAST` runs first (drops count), then `MAX_DIM` runs on survivors (caps size). No pipeline, no Pass 2, no Pass 3. |
396+
| `IMAGE_GUARD=1` | New pipeline: Pass 1 (conditional cap) + Pass 2 (request-size guard) + image-count cap. |
397+
| `IMAGE_GUARD=1` + `MAX_DIM=N` | `MAX_DIM` overrides Pass 1's conditional cap (acts as the cap value); Pass 2 still runs. |
398+
| `IMAGE_GUARD=1` + `PRESERVE_DETAIL=1` | Adds Pass 3 (Lanczos resize via `sharp`). When `sharp` unavailable, falls back to strip behavior. |
399+
| `IMAGE_GUARD=1` + `KEEP_LAST=N` | `KEEP_LAST` runs first as count cap (Pass 0); pipeline runs on remainder. |
400+
| `IMAGE_GUARD=1` + `KEEP_LAST=N` + `MAX_DIM=N` | Three-way: `KEEP_LAST` runs first; pipeline runs on remainder, but `MAX_DIM` overrides Pass 1's conditional cap; Pass 2 still runs. |
401+
| `PRESERVE_DETAIL=1` without `IMAGE_GUARD=1` | Logs warning, treats as no-op. `PRESERVE_DETAIL` is meaningless without the pipeline running. |
402+
403+
#### Tunables
404+
405+
| Env var | Default | Purpose |
406+
|---------|---------|---------|
407+
| `CACHE_FIX_IMAGE_GUARD` | unset | Top-level pipeline gate (`=1` enables). |
408+
| `CACHE_FIX_IMAGE_PRESERVE_DETAIL` | unset | Enable Pass 3 Lanczos resize via `sharp`. |
409+
| `CACHE_FIX_IMAGE_REQUEST_SIZE_MAX` | 31457280 (30 MB) | Pass 2 byte budget. 2 MB headroom from Anthropic's 32 MB ceiling. |
410+
| `CACHE_FIX_IMAGE_COUNT_MAX` | 100 | Hard image-count cap. Set to 600 for legacy Claude 1/2.x/Instant if needed. |
411+
358412
## System prompt rewrite (preload mode, optional)
359413

360414
The interceptor can rewrite Claude Code's `# Output efficiency` system-prompt section. Disabled by default. Enable with `CACHE_FIX_OUTPUT_EFFICIENCY_REPLACEMENT`. See [docs/output-efficiency-prompts.md](docs/output-efficiency-prompts.md) for the three known prompt variants and usage instructions.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Review: issue #87 image-guard design re-review
2+
3+
Date: 2026-04-27
4+
Reviewed: Issue #87 (`proxy: image-strip — full conditional pipeline matching Anthropic's actual image constraints`)
5+
Label applied: approved-by-codex-agent
6+
7+
## What Is Correct
8+
- The activation fix is now explicit enough to implement correctly. The resolution names the required loader shape directly: `image-strip` switched to `enabled: true`, then runtime-gated with `if (!process.env.CACHE_FIX_IMAGE_GUARD) return ctx;`. That matches the repo's established prefix-diff pattern used by `overage-warning` and `upstream-change-detection`, and it also adds a directive-level reminder so the mistake is less likely to recur.
9+
- The request-size guard now budgets the right unit. Measuring `Buffer.byteLength(JSON.stringify(reqCtx.body))` after Pass 1 / Pass 3 mutations and then dropping oldest images until the serialized body is back under the configured ceiling is sufficient to prevent the real `413` failure mode. The important property is the final pre-send measurement of the fully mutated body; image-only byte totals are correctly demoted to telemetry.
10+
- Pass 3's v1 re-encode policy is now safe and deterministic: resize only, preserve original media type, no automatic transcoding, `sharp` lazy-loaded as an optional peer dependency. That removes both the JPEG-to-PNG size explosion risk and the need for proxy-side content classification heuristics.
11+
- The unsupported-format policy is now explicit rather than accidental: PNG/JPEG are enforced in v1, unsupported formats fail open, and telemetry records the gap. That is an acceptable scope boundary for a first implementation.
12+
- Pass 1's sharp-unavailable behavior is clear and workable. The rejection-avoidance core does not acquire a hard native dependency; it falls back to the existing strip path, while Pass 3 logs and skips when the library is unavailable.
13+
- The telemetry schema is materially improved. It now exposes the counters needed to understand both success paths and blind spots, and it keeps `estimated_image_tokens_total` informational rather than turning it back into an enforcement signal.
14+
15+
## Blockers
16+
None.
17+
18+
## What Needs Attention
19+
- The hardcoded 100-cap model-prefix list is safe because unknown models fall back to `100`, but it is probably still worth adding `claude-3-7-sonnet-` explicitly if that family remains in circulation in any deployments. Omitting it does not create an under-enforcement risk; it only increases "unknown classification" telemetry and could cause avoidable over-trimming.
20+
- The precedence table is directionally correct but not fully exhaustive. The README should spell out the existing `KEEP_LAST=N` + `MAX_DIM=N` combination explicitly, and ideally the three-way `KEEP_LAST + IMAGE_GUARD + MAX_DIM` ordering too, instead of leaving those to inference.
21+
22+
## Recommendations
23+
- Approve the design and move to implementation with the resolved blocker text carried over verbatim into the implementation directive, especially the activation line and the exact serialized-body measurement rule.
24+
- Add `claude-3-7-sonnet-` to the 100-cap prefix list if the team still expects legacy Claude 3.7 traffic in the field.
25+
- Expand the precedence section in the README/tests so every supported env-var combination used today is described in one place.
26+
27+
## Bottom Line
28+
Approve with notes. The three blockers from the first review are resolved cleanly: activation now matches the loader's real behavior, Pass 2 budgets the actual serialized request bytes, and Pass 3 has a deterministic no-transcoding policy. The remaining nits are documentation-level refinements around model-prefix completeness and precedence-table exhaustiveness, not design blockers.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Review: issue #87 image-guard design
2+
3+
Date: 2026-04-27
4+
Reviewed: Issue #87 (`proxy: image-strip — full conditional pipeline matching Anthropic's actual image constraints`)
5+
Label applied: changes-requested
6+
7+
## What Is Correct
8+
- The issue thread converges on the right core shape: the original 4-pass design was too tied to stale assumptions, and the revised design correctly centers the two documented server-side rejection paths: conditional dimension limits and total request size.
9+
- Keeping `CACHE_FIX_IMAGE_KEEP_LAST` and `CACHE_FIX_IMAGE_MAX_DIM` as backward-compatible overrides is the right compatibility posture. The existing `image-strip` extension already composes those two behaviors in the correct order (`KEEP_LAST` first, then dimension stripping) in [proxy/extensions/image-strip.mjs](/home/manager/git_repos/claude-code-cache-fix/proxy/extensions/image-strip.mjs:144).
10+
- Adding an optional quality-preservation pass is directionally sound. The addendum correctly separates “token cost” from “image fidelity,” which matters for OCR / document-analysis workflows.
11+
- The current codebase is structurally ready for this to stay an `onRequest()` concern. Request mutation happens before the body is serialized and forwarded upstream in [proxy/server.mjs](/home/manager/git_repos/claude-code-cache-fix/proxy/server.mjs:39) and does not interact with SSE response streaming in [proxy/stream.mjs](/home/manager/git_repos/claude-code-cache-fix/proxy/stream.mjs:1).
12+
13+
## Blockers
14+
- The activation model as written does not work with the current extension loader. The issue positions `CACHE_FIX_IMAGE_GUARD=1` as the new opt-in, but `image-strip` currently exports `enabled: false` and is not present in [proxy/extensions.json](/home/manager/git_repos/claude-code-cache-fix/proxy/extensions.json:1). Disabled extensions are never loaded by [proxy/pipeline.mjs](/home/manager/git_repos/claude-code-cache-fix/proxy/pipeline.mjs:24). If env-var-only activation is the intent, the spec must explicitly switch `image-strip` to the repo’s “always loaded, runtime gated” pattern. If not, the issue needs to say that users must opt in through `extensions.json`, not just env vars.
15+
- Pass 2’s “sum bytes across all images” rule is not sufficient to reliably prevent `413 request_too_large`. The proxy forwards `Buffer.from(JSON.stringify(reqCtx.body))` as the real payload in [proxy/server.mjs](/home/manager/git_repos/claude-code-cache-fix/proxy/server.mjs:51), so the limiting quantity is final serialized request bytes, not raw image bytes. Base64 expansion, JSON envelope overhead, system prompt size, tool schemas, and non-image blocks all count. A design that only budgets image bytes can still miss the 32 MB ceiling. The spec should require measuring `Buffer.byteLength(JSON.stringify(body))` after each mutation step, with the image-only byte totals treated as telemetry rather than the enforcement signal.
16+
- Pass 3’s re-encode policy is under-specified in a way that can produce bad outcomes. “PNG for screenshot/document content, JPEG only if input was JPEG and quality is acceptable” assumes the proxy can reliably classify content type and decide acceptable JPEG quality, but neither rule is defined. Re-encoding a large JPEG to PNG can also explode request bytes and immediately work against Pass 2. The spec should pin a deterministic v1 policy, for example: preserve the original media type by default, only resize dimensions, never transcode JPEG→PNG automatically, and treat format conversion as a later follow-up if desired.
17+
18+
## What Needs Attention
19+
- The hard image-count guard needs a request-time model mapping strategy. The issue cites Anthropic’s 100-image cap for 200k-window models and 600 for others, but this proxy only knows the requested model string on the request path. The spec should define exactly how request-time classification works, what happens for unknown aliases/beta models, and whether the safe fallback is 100 or “log and do nothing.” Without that, implementations will diverge.
20+
- The current dimension helper only measures PNG and JPEG and fails open for everything else in [proxy/image-dimensions.mjs](/home/manager/git_repos/claude-code-cache-fix/proxy/image-dimensions.mjs:109). That is acceptable for the existing `MAX_DIM` safety net, but it is a blind spot for a new feature described as a general guardrail. Unsupported formats such as WebP/GIF/AVIF will bypass Pass 1 and may still 4xx. The issue should call that out explicitly and decide whether v1 accepts that limitation or expands format support.
21+
- The relationship among `CACHE_FIX_IMAGE_GUARD`, `CACHE_FIX_IMAGE_MAX_DIM`, and `CACHE_FIX_IMAGE_PRESERVE_DETAIL` needs exact precedence rules. The current text says `MAX_DIM` remains a hard override and `PRESERVE_DETAIL` is opt-in, but it does not say whether `PRESERVE_DETAIL=1` without `IMAGE_GUARD=1` is ignored, nor whether explicit `MAX_DIM` suppresses the conditional 8000/2000 logic or only overrides Pass 1 while leaving Pass 2 intact.
22+
- There is still value in telemetry for estimated aggregate image tokens even if the proxy does not trim on that basis. The docs removed the earlier “aggregate token budget” pass for good reasons, but users can still run into context-window pressure from image-heavy prompts. Reporting an approximate `estimated_image_tokens_total` would make those failures diagnosable without reintroducing an auto-trim policy.
23+
- Pass 1 still needs a crisp failure mode when resize support is unavailable. The thread uses “resize/strip” language, but the current extension only strips. If core rejection-avoidance behavior depends on decoding and re-encoding images, then the core path inherits the same dependency problem as Pass 3. The simpler v1 story is: Pass 1 strips when it cannot safely resize; Pass 3 is the only path that requires an image library.
24+
- Telemetry should include failure counters, not only successful actions. Useful fields would be: `unsupported_format_count`, `dimension_probe_fail_count`, `resize_attempted`, `resize_succeeded`, `resize_failed`, `library_missing`, `request_bytes_before`, `request_bytes_after`, and `request_bytes_headroom`. Those numbers will matter more in practice than “tokens saved.”
25+
26+
## Recommendations
27+
- Resolve activation first. The cleanest path is the same pattern already used by `overage-warning` and `upstream-change-detection`: load the extension unconditionally via config/defaults, then no-op at runtime unless `CACHE_FIX_IMAGE_GUARD=1` is set.
28+
- Define Pass 2 in terms of the real serialized request body. A practical implementation shape is: run Pass 1 / Pass 3 candidate mutations, serialize, measure `Buffer.byteLength`, and if still over budget, drop oldest images until the serialized body falls under the configured ceiling.
29+
- Narrow Pass 3’s first implementation. `sharp`-only, lazy-loaded, explicit runtime error/telemetry when enabled but unavailable is better than a vague pure-JS fallback promise. The current [package.json](/home/manager/git_repos/claude-code-cache-fix/package.json:27) and minimal [postinstall.js](/home/manager/git_repos/claude-code-cache-fix/postinstall.js:1) do not provide any install-time scaffolding for native-dependency troubleshooting, so the spec should assume runtime detection and a clear stderr message.
30+
- Preserve original media type in v1. Resize only; do not add automatic format conversion heuristics yet.
31+
- Keep the extension early in the request pipeline. The current `order: 150` is reasonable because later extensions (`smoosh-split`, `content-strip`, cache-control normalization) do not improve image sizing decisions and should not need to reason about larger-than-necessary request bodies.
32+
33+
## Bottom Line
34+
Revise before implementation. The high-level direction is good and the revised thread fixed the biggest conceptual mistake from the original body, but three implementation-shaping points are still unresolved: env-var activation does not work with the current loader, the request-size guard is budgeting the wrong unit unless it measures final serialized body bytes, and Pass 3’s format/re-encode policy is too vague to implement safely. Once those are pinned down, the rest looks like normal engineering detail rather than design risk.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Review: Image guard directive re-review
2+
3+
Date: 2026-04-27
4+
Reviewed: `docs/directives/proxy-image-guard-pipeline.md`
5+
Label applied: `reviewed-by-codex-agent`
6+
7+
## What Is Correct
8+
9+
- Blocker 1 is resolved. Pass 3 is now specified as its own independent native-cap resize pass with one trigger and one target: `IMAGE_GUARD=1` + `PRESERVE_DETAIL=1` + image long edge above native cap, resizing to 2576 px for `claude-opus-4-7*` and 1568 px otherwise. Pass 1 is separately pinned to strip-only behavior at the active rejection cap. The previously ambiguous `5000x5000` in a 5-image request case is now answered unambiguously: with `PRESERVE_DETAIL=1`, Pass 3 resizes it to 1568 px even though Pass 1's 8000 px cap would not have stripped it.
10+
- Blocker 2 is resolved. The dead `CACHE_FIX_IMAGE_COUNT_MAX_OTHER` / default-600 branch is gone. The directive now defines a single default cap of 100 with a single override env var, `CACHE_FIX_IMAGE_COUNT_MAX`, and the hard-cap section, precedence matrix, reviewer checklist, and test plan all match that shape.
11+
- The prior non-blocking notes are addressed. The earlier resize-quality overclaim is tightened to a documented blind-downscale-vs-client-controlled-resize distinction, and the `package.json` rationale now focuses on the optional install contract instead of npm-warning behavior.
12+
- Execution order is consistent across the top-level pass list, the pipeline table, the prose sections, the pipeline sketch, and the reviewer checklist: `Pass 0 -> Pass 3 -> Pass 1 -> Pass 2 -> count cap`.
13+
- The "Pass 3 runs even when Pass 1 would not have stripped" rule is now stated in prose and covered directly by test 16, with corroborating fallback coverage in tests 18 and 19.
14+
- The single `CACHE_FIX_IMAGE_COUNT_MAX` override path is explicitly tested in test 21, and no live directive references to `CACHE_FIX_IMAGE_COUNT_MAX_OTHER` remain.
15+
16+
## Blockers
17+
18+
None
19+
20+
## What Needs Attention
21+
22+
- The directive still references prior issue-thread review states in the "Source of truth" section. That is not incorrect, but it is process metadata rather than implementation guidance. If this doc is meant to age well, those references should stay secondary to the behavioral spec.
23+
- Pass 3 tests are intentionally conditional on `sharp` being importable in CI. That is acceptable for the directive, but implementation review should verify the non-`sharp` path still leaves enough exercised coverage to catch regressions in the lazy-import fallback behavior.
24+
25+
## Recommendations
26+
27+
- Proceed with implementation against this directive as written. The spec is now tight enough to hold the code to one behavior.
28+
- Keep the README precedence matrix verbatim with the directive during implementation review; the current matrix is internally consistent and should remain the single external behavior table.
29+
- During implementation review, pay particular attention to preserving the documented separation of concerns: Pass 3 resizes only, Pass 1 strips only, Pass 2 measures serialized body bytes only, and the count cap remains a final independent pass.
30+
31+
## Bottom Line
32+
33+
Approve for directive stage. The revision closes both prior blockers cleanly, addresses the two wording notes, and adds the exact missing test coverage for the above-native but below-rejection-cap case. I did not find a new contradiction or ambiguity that would justify holding the plan open.

0 commit comments

Comments
 (0)