{"record":{"id":"c37d3fa0262e3fa4","repo":"affaan-m/ECC","slug":"published-nasiko-binary-size-mismatch","errorCode":null,"errorMessage":"Published Nasiko binary size mismatch.","messagePattern":"Published Nasiko binary size mismatch\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/nasiko-release.js","lineNumber":415,"sourceCode":"      if (existing.qualified && existing.version === version) return { ...plan, dryRun: false, installed: true, reused: true };\n      throw new Error('An unqualified or incompatible Nasiko executable or receipt already exists at the destination.');\n    }\n    const retrieve = dependencies.fetchBytes || fetchBytes;\n    const manifestBytes = await retrieve(`${REGISTRY_ORIGIN}/v2/${REPOSITORY}/manifests/${release.manifestDigest}`, { accept: 'application/vnd.oci.image.manifest.v1+json', maxBytes: MAX_MANIFEST_BYTES });\n    assertDigest(manifestBytes, release.manifestDigest, 'Nasiko manifest');\n    const layer = validateManifest(manifestBytes);\n    const archiveBytes = await retrieve(`${REGISTRY_ORIGIN}/v2/${REPOSITORY}/blobs/${layer.digest}`, { maxBytes: MAX_ARCHIVE_BYTES });\n    if (archiveBytes.length !== layer.size) throw new Error('Nasiko archive size mismatch.');\n    assertDigest(archiveBytes, layer.digest, 'Nasiko archive');\n    const binary = (dependencies.extractBinary || extractQualifiedTarGzip)(archiveBytes, release.binaryName);\n    assertDigest(binary, release.binaryDigest, 'Nasiko binary');\n    if (dependencies.beforePublish) dependencies.beforePublish(destination);\n    const descriptor = fs.openSync(destination, 'wx', 0o700);\n    destinationOwned = true;\n    try {\n      fs.writeFileSync(descriptor, binary);\n      fs.fsyncSync(descriptor);\n      if (fs.fstatSync(descriptor).size !== binary.length) throw new Error('Published Nasiko binary size mismatch.');\n    } finally { fs.closeSync(descriptor); }\n    const metadata = { version, platform: release.os, architecture: release.arch, manifestDigest: release.manifestDigest, artifactDigest: layer.digest, binaryDigest: release.binaryDigest, installedPath: destination, license: release.license, sourceUrl: release.sourceUrl };\n    (dependencies.writeMetadata || writeMetadataExclusive)(metadataPath, metadata);\n    metadataOwned = true;\n    return { ...plan, dryRun: false, installed: true, reused: false, artifactDigest: layer.digest };\n  } catch (error) {\n    if (metadataOwned) fs.rmSync(metadataPath, { force: true });\n    if (destinationOwned) fs.rmSync(destination, { force: true });\n    throw error;\n  } finally { releaseLock(); }\n}\n\nfunction uninstallNasiko(options = {}, dependencies = {}) {\n  const version = options.version || 'v0.1.0';\n  const release = getQualifiedRelease(version, dependencies.platform || process.platform, dependencies.arch || process.arch);\n  const installDirectory = validateInstallDirectory(options.installDir || defaultInstallDirectory(release, dependencies.environment || process.env, dependencies.homeDirectory || os.homedir()));\n  const destination = path.join(installDirectory, release.binaryName);\n  const plan = { dryRun: Boolean(options.dryRun), version, destination };","sourceCodeStart":397,"sourceCodeEnd":433,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/nasiko-release.js#L397-L433","documentation":"Thrown after writing the Nasiko binary to disk (exclusive 'wx' open at 0700) when the file size reported by fs.fstatSync differs from the in-memory binary buffer length. It confirms the fsync'd file on disk matches what was verified in memory before the install is reported successful.","triggerScenarios":"fs.writeFileSync(descriptor, binary) plus fsync produced a file whose stat size !== binary.length — typically caused by concurrent writers to the same path, a full disk, a custom fs mock in dependencies, or filesystem corruption.","commonSituations":"Disk-full conditions during install; another process racing to write the same destination path; running under a virtualized/partial fs shim in tests that doesn't persist all bytes.","solutions":["Check free disk space on the target filesystem; free space if full and retry the install.","Ensure no concurrent process writes the same destination; the 'wx' open prevents concurrent creates but not post-open interference, so serialize installs.","Retry the install — transient I/O errors can cause short writes.","If testing with dependency-injected fs, fix the mock so writeFileSync/fstatSync report consistent sizes."],"exampleFix":"// before\nfs.writeFileSync(descriptor, binary);\nfs.fsyncSync(descriptor);\n// after (check write return + space)\nconst written = fs.writeFileSync(descriptor, binary);\nfs.fsyncSync(descriptor);\nif (fs.statSync(destination).size !== binary.length) throw new Error('short write — check disk space');","handlingStrategy":"try-catch","validationCode":"import fs from 'node:fs';\nimport { statfsSync } from 'node:fs';\nconst space = statfsSync(path.dirname(destination));\nif (space.bavail * space.bsize < binaryLength) throw new Error('insufficient disk space');","typeGuard":"null","tryCatchPattern":"try {\n  await installNasiko({ version });\n} catch (e) {\n  if (e.message === 'Published Nasiko binary size mismatch.') {\n    // free disk space / stop concurrent writers, then retry\n    await installNasiko({ version });\n  } else throw e;\n}","preventionTips":["Monitor free disk space on the install filesystem","Serialize installs to avoid concurrent writes to the same destination","Avoid partial fs mocks in tests that skew fstat results","Retry installs after transient I/O errors"],"tags":["filesystem","integrity","install"],"backgroundTag":"internal-invariant-violation","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}