Skip to content

http.compressionThreshold: docs say default 1200, shipped config sets 0 (compression off by default) #656

Description

@Ethan-Arrowood

What's wrong

The docs state that http.compressionThreshold defaults to 1200 bytes, so Brotli response compression is on out of the box. Harper's shipped configuration sets it to 0, and the runtime treats 0 as "off" — so on a default install, Brotli response compression never happens.

The root cause is not in this repo. Harper contradicts itself, and our docs faithfully mirror the half of it that isn't what actually ships.

Evidence (harper origin/main)

Location Says
config-root.schema.json:25-31 property description: "Responses larger than this threshold (bytes) will be compressed... Default: 1200"
config-root.schema.json:774 examples block: "compressionThreshold": 1200
static/defaultConfig.yaml:4 compressionThreshold: 0 — this is what gets written into a new install's config
validation/configValidator.ts:329 compressionThreshold: number.optional() — no default injected
server/serverHelpers/contentTypes.ts:362 const COMPRESSION_THRESHOLD = envMgr.get(CONFIG_PARAMS.HTTP_COMPRESSIONTHRESHOLD) — no || fallback, so a configured 0 stays 0
server/serverHelpers/contentTypes.ts:373 canCompress = COMPRESSION_THRESHOLD && request.headers...includes('br') — 0 is falsy, so compression is skipped entirely

So the schema documents 1200, the shipped template sets 0, and the runtime honors 0. There is no code-level 1200 default anywhere.

Note this is specifically http.compressionThreshold. The unrelated storage.compressionThreshold (LMDB record compression) does have a real code default — (storage.pageSize || 4096) - 60 at resources/databases.ts:102 — which makes this easy to conflate when grepping.

Affected pages in this repo

The question that has to be answered first

Is compressionThreshold: 0 in the shipped config intentional? The docs fix depends entirely on the answer, and the two outcomes are very different:

  • If 0 is a bug in harper - Brotli compression has been silently off for every default install. That is the real finding here, and the fix belongs in static/defaultConfig.yaml. Our docs then need no change beyond possibly noting the versions affected.
  • If 0 is intentional - then harper's own schema description and examples are wrong too, and our four locations should say compression is disabled by default and must be explicitly enabled. In that case config-root.schema.json should be corrected alongside.

Either way one of the two harper locations is wrong, so this likely wants a companion harper issue; transferring or cross-linking is fine.

Why it matters beyond a wrong number

Two things downstream depend on this:

  1. Anyone following our docs believes responses over 1200 bytes are compressed. If they are not, that is a real and invisible performance difference on a default install.
  2. There is a latent crash gated behind this setting. contentTypes.ts:410-425 pipes serializeStream's return value into createBrotliCompress() whenever canCompress is true, but the application/x-msgpack handler returns a Buffer (not a stream) for a plain array. Accept: application/x-msgpack + Accept-Encoding: br + an array body would throw TypeError: stream.pipe is not a function. That is currently unreachable only because compression ships disabled - so if the default is "corrected" to 1200 without fixing the pipe path first, this becomes reachable the same day. Sequencing matters. Surfaced in docs(http): correct serializeStream return type - follow-up to #641 #655.

Suggested fix

  1. Get a ruling from whoever owns static/defaultConfig.yaml on whether 0 is intended.
  2. Fix harper's inconsistency (either the shipped default or the schema description + examples).
  3. Update the four locations above to match, in one pass so they cannot disagree with each other.
  4. Track the .pipe() normalization separately, and land it before any change that turns compression on by default.

sent with Claude Opus 5

Activity

  1. added
    content📝 Content specific issues and requests - text, examples, missing info, or clarity
    on Aug 31, 2026
  2. Ethan-Arrowood commented on Aug 31, 2026

    @Ethan-Arrowood
    MemberAuthor

    Adding a second, distinct error found while fixing #655 — same root cause, but a separate wrong claim.

    Two locations say streaming responses are compressed regardless of this setting. They are not.

    The streaming compression branch sits inside the same gate as everything else. server/serverHelpers/contentTypes.ts:

    let stream = serializer.serializer.serializeStream(responseData, responseObject);
    if (canCompress) {                                  // <- same gate
        responseObject.headers.set('Content-Encoding', 'br');
        stream = stream.pipe(createBrotliCompress({ ... }));

    with canCompress = COMPRESSION_THRESHOLD && ...accept-encoding...includes('br') at :373. So a falsy threshold disables compression for streaming responses too. There is no bypass.

    The "since their size is unknown upfront" reasoning is sound as far as it goes - a streaming response cannot be compared against a byte threshold, so the threshold is not applied as a size test. But it is still used as an on/off flag, which is exactly what the current wording denies.

    So the fix has two parts, not one:

    1. The default value (0 shipped vs 1200 documented) - the original report above.
    2. This: the threshold is a feature flag as well as a size threshold, and streaming responses are gated by it. Whatever the ruling on part 1, this sentence is wrong today in both places.

    Both were verified against harper origin/main. Full count of affected locations is six: configuration/options.md:37, http/configuration.md:111/:119/:123/:318, http/overview.md:104, configuration/operations.md:106.

    One caution for whoever picks this up: the v4 versioned copies under reference_versioned_docs/version-v4/ carry the same claims and should be checked against a v4 harper ref before editing - do not assume v4 also shipped 0.

    sent with Claude Opus 5

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    content📝 Content specific issues and requests - text, examples, missing info, or clarity

    Type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions