Skip to content

attw broken by [email protected] - tarball extraction keeps only the empty final-flush chunk #262

Closed as duplicate

Description

@ljharb

Summary

attw -P and attw --from-npm <anything> crash with Cannot read properties of undefined (reading 'filename') when [email protected] is hoisted into @arethetypeswrong/core's tree. 0.8.3 changed the streaming Gunzip to emit multiple callbacks where 0.8.2 emitted one; createPackageFromTarballData only keeps the last callback's chunk, which under 0.8.3 is always an empty final-flush chunk.

@arethetypeswrong/[email protected] declares "fflate": "^0.8.2", so a fresh install resolves to 0.8.3 and the tool is broken for any package, regardless of size.

Reproduction

One command - any published package works:

npx -p @arethetypeswrong/[email protected] attw --from-npm [email protected]
# → error while checking package:
# →   Cannot read properties of undefined (reading 'filename')

Confirmation that it's the fflate version

# in a project that has @arethetypeswrong/cli installed
npm i --no-save [email protected]
npx attw --from-npm [email protected]   # ✅ works, prints the result table
npm i --no-save [email protected]
npx attw --from-npm [email protected]   # ❌ crashes

Why it happens

packages/core/src/createPackage.ts (built form in dist/createPackage.js:228):

let unzipped;
new Gunzip((chunk) => (unzipped = chunk)).push(tarball, /*final*/ true);
const data = untar(unzipped);
const prefix = data[0].filename.substring(...);  // throws when data is empty

Instrumented Gunzip callbacks against the [email protected] tarball:

version callbacks
[email protected] [{ len: 10752, final: true }]
[email protected] [{ len: 10752, final: false }, { len: 0, final: false }, { len: 0, final: true }]

Because unzipped = chunk overwrites on each callback, unzipped ends up as the empty final chunk under 0.8.3. untar(empty) returns []. data[0] is undefined. .filename throws.

The comment in the source ("Use streaming API to work around 101arrowz/fflate#207") explains why gunzipSync was avoided, but the streaming code assumed exactly one callback - which 0.8.2 happened to provide.

Suggested fix

Accumulate chunks rather than overwriting, e.g.:

const chunks: Uint8Array[] = [];
new Gunzip((chunk) => chunks.push(chunk)).push(tarball, /*final*/ true);
let total = 0;
for (const c of chunks) total += c.length;
const unzipped = new Uint8Array(total);
let offset = 0;
for (const c of chunks) { unzipped.set(c, offset); offset += c.length; }
const data = untar(unzipped);

Alternatively, revisit gunzipSync — fflate#207 may already be fixed, in which case gunzipSync(tarball) is simpler. A short-term option is to tighten the fflate dependency to ~0.8.2 until a real fix lands.

Environment

Activity

  1. ericcornelissen commented on May 23, 2026

    @ericcornelissen

    Duplicate of #258(?)

  2. ljharb commented on May 24, 2026

    @ljharb
    ContributorAuthor

    yup, thanks

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions