Skip to content

Add HttpServer response compression middleware - #6898

Merged
tim-smart merged 10 commits into
mainfrom
agent/claude-engineer/c7e7518d
Aug 3, 2026
Merged

Add HttpServer response compression middleware#6898
tim-smart merged 10 commits into
mainfrom
agent/claude-engineer/c7e7518d

Conversation

@tim-smart

@tim-smart tim-smart commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Implements the response compression spec for the v4 HttpServer.

Shared middleware (effect/unstable/http)

  • HttpMiddleware.compression(options?) with algorithms (default ["br", "gzip", "deflate"], zstd opt-in), minSize (default 1024), compressible predicate, and per-algorithm levels (gzip 6, deflate 6, br 4, zstd 3).
  • Accept-Encoding negotiation per RFC 9110: q-values decide acceptability only, server order decides ranking, * matches unlisted codings, malformed headers are treated as absent, and no acceptable coding sends identity with 200 (never 406).
  • Skip conditions: 1xx/204/206/304, existing Content-Encoding (with identity stripped as the opt-out), Cache-Control: no-transform, non-compressible or absent content type, known length below minSize, Empty/FormData bodies. HEAD requests mirror GET headers.
  • Vary: Accept-Encoding is appended (no duplicates, Vary: * untouched) on every eligible response, including ones skipped by negotiation or minSize.
  • Applied via the pre-response handler mechanism (same as cors) so it composes with HttpEffect.toWebHandler, HttpRouter.serve, and the platform servers.

HttpPlatform.compression primitive

  • New effectful compression: { algorithms, compressResponse } member on the service; HttpPlatform.make wraps the platform transform with shared header logic (Content-Encoding, Vary, strong ETags weakened to W/...).
  • All Uint8Array bodies on Node.js, Bun, and Deno use the asynchronous node:zlib callback APIs through Effect.callback. The exact compressed Content-Length comes from result.byteLength; there is no sync path or size-based switch to streaming.
  • Stream and Raw bodies remain streaming transforms and drop Content-Length.
  • Platform coverage matches the spec table:
    • Node: all four via node:zlib (zstd gated on runtime support); async one-shot APIs for Uint8Array bodies and zlib transforms with per-chunk flush for Stream/Raw bodies.
    • Bun: all four via Bun's native node:zlib async implementations for one-shot bodies and extended CompressionStream for streaming, including BunFile responses. No Bun.*Sync one-shot branch is used.
    • Deno: all four via node:zlib compatibility one-shot APIs; streaming gzip/deflate use CompressionStream, while br/zstd use node:zlib compatibility streams.
    • Web: gzip/deflate via CompressionStream; byte arrays are necessarily sent through a single-chunk stream because node:zlib is unavailable.

Tests

  • Shared conformance coverage exercises negotiation, q-values, wildcard/identity, malformed headers, minSize, content types, opt-outs, no-transform, status handling, HEAD, incremental streaming, ETag weakening, Vary merging, and cookies.
  • Node, Bun, and Deno tests assert exact Content-Length for async one-shot bodies and continue to assert absent Content-Length for stream/file responses.
  • Runtime suites cover gzip, deflate, brotli, zstd where supported, file response cleanup, and incremental Node streaming.

Validation:

  • pnpm lint-fix
  • pnpm check
  • pnpm --dir packages/platform-bun check
  • pnpm --dir packages/platform-deno check
  • Node/shared targeted Vitest suites: 47 tests passed
  • Bun platform Vitest suite: 10 tests passed
  • Deno compression Vitest suite: 8 tests passed

Closes EFF-332
Closes EFF-352

🤖 Generated with Claude Code

Implements Accept-Encoding negotiation (RFC 9110), skip logic, Vary
handling, and ETag weakening in shared http modules, with a new
HttpPlatform compression primitive implemented via native APIs on
Node.js, Bun, Deno, and web handlers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-project-automation github-project-automation Bot moved this to Discussion Ongoing in PR Backlog Aug 3, 2026
@changeset-bot

changeset-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cd0a61e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
effect Patch
@effect/platform-node Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-node-shared Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@effect-slopcop effect-slopcop Bot added 4.0 enhancement New feature or request labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
basic.ts 7.06 KB 7.06 KB 0.00 KB (0.00%)
batching.ts 9.86 KB 9.86 KB 0.00 KB (0.00%)
brand.ts 6.34 KB 6.34 KB 0.00 KB (0.00%)
cache.ts 10.62 KB 10.62 KB 0.00 KB (0.00%)
config.ts 20.60 KB 20.60 KB 0.00 KB (0.00%)
differ.ts 20.20 KB 20.20 KB 0.00 KB (0.00%)
http-client.ts 21.49 KB 21.49 KB 0.00 KB (0.00%)
logger.ts 10.76 KB 10.76 KB 0.00 KB (0.00%)
metric.ts 8.98 KB 8.98 KB 0.00 KB (0.00%)
optic.ts 7.18 KB 7.18 KB 0.00 KB (0.00%)
pubsub.ts 14.90 KB 14.90 KB 0.00 KB (0.00%)
queue.ts 11.58 KB 11.58 KB 0.00 KB (0.00%)
schedule.ts 10.74 KB 10.74 KB 0.00 KB (0.00%)
schema-class.ts 19.14 KB 19.14 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 28.96 KB 28.96 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 25.29 KB 25.29 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.30 KB 13.30 KB 0.00 KB (0.00%)
schema-string.ts 10.94 KB 10.94 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.17 KB 15.17 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 21.94 KB 21.94 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.34 KB 24.34 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.18 KB 19.18 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 19.01 KB 19.01 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.87 KB 18.87 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.60 KB 22.60 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.52 KB 19.52 KB 0.00 KB (0.00%)
schema.ts 18.41 KB 18.41 KB 0.00 KB (0.00%)
stm.ts 12.54 KB 12.54 KB 0.00 KB (0.00%)
stream.ts 9.80 KB 9.80 KB 0.00 KB (0.00%)

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ One rough edge worth a look, otherwise solid.

Reviewed changes

  • Compression middleware in HttpMiddleware — negotiates Accept-Encoding per RFC 9110 (q-values for acceptability, server order for ranking, wildcard matching), skips ineligible responses (status, existing encoding, no-transform, content type, minSize, empty/FormData bodies), manages Vary: Accept-Encoding, and applies via the pre-response handler mechanism.
  • HttpPlatform compression primitive — new Compression interface with algorithms advertisement and compressResponse; wrapCompression layers on Content-Encoding, Vary, and ETag weakening; makeCompression helper for Web-stream-based platforms with optional sync one-shot.
  • Platform implementations — Node (all four algorithms via node:zlib with per-chunk flush for SSE), Bun (Bun.gzipSync/Bun.deflateSync/Bun.zstdCompressSync + node:zlib brotli + extended CompressionStream), Deno (CompressionStream gzip/deflate + node:zlib compatibility for br/zstd), Web fallback (gzip/deflate via CompressionStream).
  • HttpServerResponse.removeHeader — new combinator needed for Content-Encoding: identity stripping and content-length removal on streaming compression.
  • Tests — conformance matrix under web handler (negotiation, q-values, wildcards, malformed headers, minSize, content types, opt-outs, no-transform, status codes, HEAD, streaming, ETag weakening, Vary merging, cookies); platform integration tests for Node and Deno (advertised algorithms, exact Content-Length, br/zstd round-trips, file responses, end-to-end server, SSE incremental flush deadlock).
  • Changeset — patches effect, @effect/platform-node, @effect/platform-bun, @effect/platform-deno.

The Bun platform implementation is untested in CI (no Bun test infrastructure in this repo). The PR description notes it was verified locally — no issue, just recording the gap for posterity.


Note: 1 inline comment(s) dropped because they did not anchor to lines inside the PR diff:

  • packages/platform-node/src/NodeHttpPlatform.ts:1439-1440 (RIGHT) — line 1440 (RIGHT) is not inside a diff hunk

Pullfrog  | Fix it ➔View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

Comment thread packages/effect/src/unstable/http/HttpMiddleware.ts Outdated
Comment thread packages/effect/src/unstable/http/HttpMiddleware.ts Outdated
Comment thread packages/effect/src/unstable/http/HttpPlatform.ts Outdated
@pullfrog

pullfrog Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

(The one inline comment about packages/platform-node/src/NodeHttpPlatform.ts line 119 didn't anchor — here's what I meant:)

In the Raw body case of compressResponse, only readable.on("error", …) is wired. If the zlib transform itself errors (independent of the source stream), stream.pipe() unpipes the source but doesn't destroy it — the readable stays open. Consider adding the reverse direction: transform.on("error", (cause) => readable.destroy(cause)).

Pullfrog  | View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

Comment thread .changeset/eff-332-http-response-compression.md Outdated
return ServerResponse.setBody(
response,
HttpBody.uint8Array(compressSync(body.body, algorithm, options), body.contentType)
return ServerResponse.stream(

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.

I think we should use a .raw response for this case

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.

Addressed in efc8462: the Uint8Array case now returns ServerResponse.raw around Readable.from([body.body]).pipe(compressTransform(...)), while preserving the response metadata and dropping Content-Length.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes since the prior Pullfrog review:

  • Removed compressSync from makeCompression — the sync one-shot path is gone; all compression (including Uint8Array bodies) now goes through the streaming path, dropping Content-Length on every compressed response. This simplifies the API and avoids the maintenance cost of dual code paths.
  • Inlined CompressionMiddlewareOptions as an anonymous object type on the compression function parameter, keeping the public interface self-contained.
  • Added identity guard in wrapCompression (compressed === response) — prevents stamping content-encoding, vary, and ETag headers on responses that compressResponse returned unchanged (e.g. Raw bodies with null content that couldn't be transformed).
  • Added bidirectional error forwarding in Node Raw body casetransform.on("error", (cause) => readable.destroy(cause)) now complements the existing source→transform direction, fixing the gap noted in the initial review.
  • Added ZSTD_e_flush and flush control to Deno's zstd path — mirrors Node's SSE-friendly per-chunk flush behavior; includes node.d.ts type updates.
  • Removed BROTLI_PARAM_SIZE_HINT from brotliParams — the size hint was only relevant for the removed sync one-shot path and has no place in the streaming approach.
  • Removed redundant |\* alternative from acceptMember regex — the * character is already present in the character class; simplifies without changing behavior.
  • Moved module-level constants (defaultAlgorithms, defaultLevels, noTransformRegex, contentLengthHeader) below the compression function.
  • Updated tests — assertion changes from exact Content-Length to null for all compressed responses; new test "does not stamp headers when a Raw body cannot be transformed" exercises the identity guard; cleaned up test option types.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes since the prior Pullfrog review:

  • Node Uint8Array compression now uses ServerResponse.raw — the compressResponse Uint8Array case was changed from ServerResponse.stream(NodeStream.pipeThroughDuplex(Stream.succeed(body.body), ...), ...) to ServerResponse.raw(Readable.from([body.body]).pipe(compressTransform(...)), ...). This avoids wrapping a single-chunk body in an Effect Stream only to pipe it through a duplex transform, instead creating a Node Readable directly and piping it into the compression transform. The response metadata (status, headers, cookies, Content-Length removal) is unchanged, and wrapCompression continues to layer on Content-Encoding, Vary, and ETag weakening. Error propagation is handled by .pipe() — the source is a trivial in-memory readable that does not need explicit bidirectional forwarding.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

Comment thread packages/effect/src/unstable/http/HttpPlatform.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes since the prior Pullfrog review:

  • Made compression a required member of HttpPlatform.make — the compression parameter on HttpPlatform.make and the compression field on the HttpPlatform service type were changed from optional (compression?: Compression | undefined) to required (compression: Compression). The ?? compressionWeb() fallback inside make was removed, and the layer factory now explicitly passes compression: compressionWeb().
  • Updated test stubs in HttpStaticServerConditional.test.ts — three HttpPlatform.of() calls now include compression: stubCompression (a no-op stub with an empty algorithm set and identity compressResponse).

The middleware's runtime fallback (Context.getOrUndefined + ?? compressionWebWrapped()) is unchanged — it still handles the case where HttpPlatform is absent from the context.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

- Share one CompressionStream transform helper across web, Bun, and Deno
- Rebuild compressed responses via setBody/removeHeader instead of
  hand-spreading response fields at five sites
- Merge the Node Uint8Array and Raw cases, drop the Blob copy for
  one-chunk bodies, and replace lazy singletons with plain consts
- Hoist per-request work in the middleware and trim dead declarations
  from the Deno node compat shim

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes since the prior Pullfrog review:

  • compressionWeb/compressionWebWrapped now module-level constants — changed from lazily-initialized closures to eager constants since makeCompression and wrapCompression return plain objects with no runtime state dependency.
  • Shared compressionStream utility — extracted the CompressionStream-backed transform as a reusable helper, exported on HttpPlatform for platform use (Bun, Deno).
  • compressedBody/setBodyWithoutLength/streamBody/singleChunkStream helpers — factored out repeated setBody → removeHeader("content-length") patterns, replacing manual response reconstruction with HttpBody constructors that preserve all response fields via setBody.
  • Node Uint8Array case merged into RawUint8Array bodies now create Readable.from([body.body]) and fall through to the unified Raw handling with bidirectional error forwarding (harmless for in-memory readables).
  • Deno/Bun gzip/deflate now use Platform.compressionStream — switched from inline stream.pipeThrough(new CompressionStream(...)) to the shared utility.
  • withVary extracted as module-level function — moved from closure-local to module scope for clarity.
  • accept-encoding read inside pre-response handler — reads the header from the handler's request parameter instead of capturing it in the outer closure; behaviorally identical.
  • node.d.ts type cleanup — removed ZlibCompressOptions (no longer needed), inlined flush into BrotliOptions.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes since the prior Pullfrog review:

  • Made HttpPlatform required in the compression middlewareContext.getUnsafe replaces the getOrUndefined + compressionWebWrapped fallback, adding HttpPlatform to the middleware's required dependency signature. Every call path that uses the middleware (via HttpServer.layerServices, HttpPlatform.layer, or a platform-specific layer) already provides HttpPlatform, so the requirement is always satisfied. compressionWebWrapped is removed as dead code.
  • Renamed compressionStreamcompressionTransformWeb — makes the Web-stream-based nature explicit. All callers updated (Bun, Deno, internal compressionWeb).
  • Renamed makeCompressionmakeCompressionWeb — same motivation. All callers updated (Bun, Deno).
  • Test harness improvementsmakeHandler now uses toWebHandlerLayer with HttpServer.layerServices for the default path (with afterAll disposal via a disposers array), and toWebHandlerWith for explicit-context negotiation tests. platformContext/negotiationPlatformContext helpers properly typed via HttpPlatform.of() without as unknown as casts.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes since the prior Pullfrog review:

  • HEAD request body cancellation in Node — added cancelResponseBody that destroys Readable streams for Raw bodies when handling HEAD requests, preventing file descriptor leaks when compression middleware runs on file responses with a HEAD verb.
  • Deno transform close cleanup — added transform.on("close", () => source.destroy()) to the br/zstd compression path, ensuring the source Readable is cleaned up when the compression transform closes (complementing the existing source→transform error forwarding).
  • Deno node.d.ts type updates — added on("close", ...) and destroy(cause?) to the Readable type declarations to support the close cleanup code.
  • Bun compression tests in CI — new BunHttpCompression.test.ts covering one-shot, stream, and Bun.file compression round-trips for all four algorithms; backed by a test-bun CI job under Bun 1.3.13 and a @effect/platform-bun vitest project entry.
  • BREACH security documentation — added a Security section to the compression JSDoc explaining BREACH-style attack risks and mitigations (Content-Encoding: identity, Cache-Control: no-transform, compressible predicate).
  • HEAD close test in Node compression suite — new test verifies that compressed file bodies are properly closed (file descriptor released) when an HTTP HEAD request is served.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes since the prior Pullfrog review:

  • compressResponse made effectful — the Compression.compressResponse method and the transform function now return Effect.Effect<HttpServerResponse> instead of HttpServerResponse, enabling asynchronous one-shot compression. wrapCompression maps over the effect to layer on Content-Encoding, Vary, and ETag weakening.
  • Async one-shot Uint8Array compression — Node, Bun, and Deno platforms use node:zlib callback APIs (Zlib.gzip, Zlib.deflate, Zlib.brotliCompress, Zlib.zstdCompress) wrapped in Effect.callback for Uint8Array bodies, producing an exact Content-Length from the compressed result. Stream and raw bodies remain on the existing streaming transform paths.
  • makeCompressionWeb wraps sync returns in Effect.succeed — the Web-stream-based compression helper now conforms to the effectful compressResponse signature; compressionWeb remains a module-level constant.
  • node.d.ts extended — added ZlibCompressOptions, CompressCallback, and async callback API declarations (gzip, deflate, brotliCompress, zstdCompress) for Deno's TypeScript build compatibility.
  • Tests assert exact Content-Length — platform-level one-shot compression tests now verify compressed.byteLength.toString() instead of null, and zstdSupported detection uses typeof Zlib.zstdCompress consistently across all platforms.
  • stubCompression and test fixtures updatedcompressResponse on stubs uses Effect.succeed; HttpStaticServerConditional test fixtures pass the new required compression field.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

The one-shot zlib compression, streaming transforms, and algorithm
detection were copy-pasted across the Node, Bun, and Deno platforms.
Move them to a shared NodeHttpCompression module, which also removes
the Deno-local node:zlib/node:stream type shims.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes since the prior Pullfrog review:

  • Extracted shared NodeHttpCompression module in @effect/platform-node-shared — the async one-shot Uint8Array compression, streaming Duplex/Web ReadableStream transforms, and zstd runtime detection that were duplicated across Node, Bun, and Deno now live in a single source file. Bun and Deno delegate Uint8Array handling to NodeHttpCompression.make() wrapped around their existing makeCompressionWeb streaming fallback; Node delegates Stream/Raw handling to its own fallback while make() handles Uint8Array.
  • Removed Deno's internal/node.d.ts — the node:zlib and node:stream type stubs are no longer needed since NodeHttpCompression.ts lives in platform-node-shared which has native Node type support.
  • Updated barrel exportsplatform-node-shared/src/index.ts now exports NodeHttpCompression.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@tim-smart
tim-smart merged commit c0a1534 into main Aug 3, 2026
18 checks passed
@tim-smart
tim-smart deleted the agent/claude-engineer/c7e7518d branch August 3, 2026 23:41
@github-project-automation github-project-automation Bot moved this from Discussion Ongoing to Done in PR Backlog Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant