Budibase/budibase · error · HTTPError

Project package is invalid.

Error message

Project package is invalid.

What it means

validateProjectPackageBeforeExtraction reads the first two bytes of the uploaded file and verifies the gzip magic number (0x1f 0x8b). If the header bytes do not match, the file is not a gzip archive and this HTTPError (400) is thrown before any extraction is attempted.

Source

Thrown at packages/server/src/sdk/workspace/projects/backups/imports.ts:230

      files.push(fullPath)
    }
  }

  return files
}

const validateProjectPackageBeforeExtraction = async (file: {
  path: string
}) => {
  const archiveHeader = new Uint8Array(2)
  const archiveFile = await fsp.open(file.path, "r")
  try {
    await archiveFile.read(archiveHeader, 0, archiveHeader.length, 0)
  } finally {
    await archiveFile.close()
  }
  if (archiveHeader[0] !== 0x1f || archiveHeader[1] !== 0x8b) {
    throw new HTTPError("Project package is invalid.", 400)
  }

  const totals = { files: 0, bytes: 0 }
  const stream = fs.createReadStream(file.path)
  let entries = 0

  let validationError: HTTPError | undefined
  const fail = (error: HTTPError) => {
    validationError = error
    stream.destroy(error)
  }

  const parser = tar.list({
    onReadEntry: (entry: ProjectPackageTarEntry) => {
      if (validationError) {
        return
      }
      entries += 1

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Ensure the package is gzip-compressed: create it with `tar -czf project.tar.gz <dir>` and verify with `file project.tar.gz` (should report 'gzip compressed data').
  2. Re-download/re-export the package; compare checksums to rule out truncation or corruption in transit.
  3. Check that the client posts the correct file field and is not sending a different export artifact.
  4. If exporting programmatically, confirm gzip compression step runs before upload.

Example fix

// before: uncompressed tar uploaded as project package
tar -cf project.tar ./app && curl -F file=@project.tar ...
// after: gzip-compressed package
tar -czf project.tar.gz ./app && curl -F file=@project.tar.gz ...
Defensive patterns

Strategy: validation

Validate before calling

import { openSync, readSync, closeSync } from "fs"

function isGzipFile(path: string): boolean {
  const fd = openSync(path, "r")
  try {
    const header = Buffer.alloc(2)
    readSync(fd, header, 0, 2, 0)
    return header[0] === 0x1f && header[1] === 0x8b
  } finally {
    closeSync(fd)
  }
}

if (!isGzipFile(file.path)) throw new Error("Not a gzip archive")

Try / catch

try {
  await api.importProjectPackage(file)
} catch (err) {
  if (err?.status === 400 && err?.message === "Project package is invalid.") {
    // verify the upload is a gzip .tar.gz and re-export if needed
  } else throw err
}

Prevention

When it happens

Trigger: Calling the project import/restore API with a file whose first two bytes are not 0x1f,0x8b — e.g. a plain (uncompressed) tar, a zip file, a JSON export, a truncated upload, or any non-archive file.

Common situations: Uploading an uncompressed .tar instead of .tar.gz; a proxy or browser mangled/truncated the upload; user renamed a zip to .tar.gz; uploading an old export format (e.g. plain JSON app export) to the new package import endpoint.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/3da5ad849a752e07. Report an issue: GitHub.