Skip to content

desktop: "Export debug logs" zip can't be opened by macOS Archive Utility #54828

Closed

Description

@lucasdu4rte

Description

The zip produced by Export debug logs fails to open in Finder / Archive Utility on macOS. ditto fails the same way:

$ ditto -x -k opencode-debug-20261012T143607.zip out
ditto: manifest.json: No such file or directory
ditto: Couldn't read pkzip signature.

The archive itself is valid: unzip -t and bsdtar read all 24 entries. Every entry sets general-purpose bit 3 (sizes in a trailing data descriptor), but the descriptors omit the optional PK\x07\x08 signature, and Archive Utility stops at the first one. After inserting the signature before each descriptor (and fixing the central directory offsets), ditto extracts all 24 files.

Writing the data descriptor signature, as most zip writers do, should fix it.

OpenCode version

2.0.27 (desktop, Electron 44.4.5)

Steps to reproduce

  1. In OpenCode Desktop on macOS, run Export debug logs.
  2. Double-click the zip in Finder, or run ditto -x -k <zip> out.

Operating System

macOS (Darwin 27.0.0), arm64

Activity

  1. opencode-agent commented on Oct 12, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I confirmed the cause. The desktop app builds this zip with @zip.js/zip.js 2.7.62, using its default ZipWriter options (writeZip in packages/desktop/src/main/native/logging.ts). With those defaults, every entry sets bit 3 and writes its data descriptor without the PK\x07\x08 signature. In my test, the bytes where the signature should be were the CRC, and unzip -t still reported no errors, which matches what you saw.

    The script below reproduces this. It builds an archive the same way and checks each descriptor. The code is unchanged on the latest dev (e41e7e9), so the problem is still there. Passing { dataDescriptorSignature: true } to the ZipWriter adds the signature to every descriptor. I couldn't check ditto or Archive Utility myself because this sandbox doesn't run macOS.

    Reproduction script (repro.ts)
    // Reproduces #54828: mirrors writeZip() in packages/desktop/src/main/native/logging.ts
    // (zip.js ZipWriter with default options, as in Desktop 2.0.27) and inspects the output.
    import { BlobReader, BlobWriter, ZipWriter } from "@zip.js/zip.js"
    import { writeFileSync } from "node:fs"
    
    const entries = [
      { name: "manifest.json", data: Buffer.from(JSON.stringify({ hello: "world" }, null, 2)) },
      { name: "desktop/main.log", data: Buffer.from("log line\n".repeat(100)) },
    ]
    const writer = new ZipWriter(new BlobWriter("application/zip"))
    for (const e of entries) await writer.add(e.name, new BlobReader(new Blob([new Uint8Array(e.data)])))
    const zip = Buffer.from(await (await writer.close()).arrayBuffer())
    writeFileSync("/workspace/repro/out.zip", zip)
    
    // Walk the local headers
    let off = 0, ok = true
    while (zip.readUInt32LE(off) === 0x04034b50) {
      const flags = zip.readUInt16LE(off + 6)
      const nameLen = zip.readUInt16LE(off + 26), extraLen = zip.readUInt16LE(off + 28)
      const name = zip.toString("utf8", off + 30, off + 30 + nameLen)
      // find compressed size from central directory
      const cdIdx = zip.indexOf(Buffer.concat([Buffer.from([0x50, 0x4b, 1, 2])]))
      let p = cdIdx, csize = -1
      while (zip.readUInt32LE(p) === 0x02014b50) {
        const n = zip.toString("utf8", p + 46, p + 46 + zip.readUInt16LE(p + 28))
        if (n === name) csize = zip.readUInt32LE(p + 20)
        p += 46 + zip.readUInt16LE(p + 28) + zip.readUInt16LE(p + 30) + zip.readUInt16LE(p + 32)
      }
      const dd = off + 30 + nameLen + extraLen + csize
      const hasSig = zip.readUInt32LE(dd) === 0x08074b50
      console.log(`${name}: bit3=${(flags & 8) !== 0} descriptorSignature=${hasSig} bytes@descriptor=${zip.subarray(dd, dd + 4).toString("hex")}`)
      if ((flags & 8) && !hasSig) ok = false
      off = dd + (hasSig ? 16 : 12)
    }
    console.log(ok ? "OK: descriptors are signed" : "BUG: data descriptors lack the PK\\x07\\x08 signature (macOS ditto/Archive Utility reject this)")
    process.exit(ok ? 0 : 1)

    Run it: bun repro.ts

  2. Dante-dan commented on Oct 12, 2026

    @Dante-dan
    Contributor

    I’m taking this fix. I reproduced the same ditto failure on macOS with the default ZipWriter options, and extraction succeeds with dataDescriptorSignature: true. I’ll keep the change scoped to the debug-log archive writer.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions