Tencent/tinker · error · ZipException

No active entry

Error message

No active entry

What it means

TinkerZipOutputStream.write(byte[], int, int) throws ZipException("No active entry") when currentEntry == null — i.e. there is no entry between putNextEntry() and closeEntry(). The zip format requires all payload bytes to belong to a local file header, so writing outside an entry would produce a corrupt archive and is rejected eagerly.

Source

Thrown at third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipOutputStream.java:576

            this.commentBytes = BYTE;
            return;
        }
        byte[] newCommentBytes = comment.getBytes(StandardCharsets.UTF_8);
        checkSizeIsWithinShort("Comment", newCommentBytes);
        this.commentBytes = newCommentBytes;
    }

    /**
     * Writes data for the current entry to the underlying stream.
     *
     * @throws IOException
     *                If an error occurs writing to the stream
     */
    @Override
    public void write(byte[] buffer, int offset, int byteCount) throws IOException {
        Arrays.checkOffsetAndCount(buffer.length, offset, byteCount);
        if (currentEntry == null) {
            throw new ZipException("No active entry");
        }
        /*final long totalBytes = crc.tbytes + byteCount;
        if ((totalBytes > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) && !currentEntryNeedsZip64) {
            throw new IOException("Zip entry size (" + totalBytes +
                    " bytes) cannot be represented in the zip format (needs Zip64)." +
                    " Set the entry length using ZipEntry#setLength to use Zip64 where necessary.");
        }*/
        if (currentEntry.getMethod() == STORED) {
            out.write(buffer, offset, byteCount);
        } else {
            out.write(buffer, offset, byteCount);
        }
        // crc.update(buffer, offset, byteCount);
    }
    private void checkOpen() throws IOException {
        if (cDir == null) {
            throw new IOException("Stream is closed");
        }

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Check the call ordering: every write() must sit between a successful putNextEntry(e) and the matching closeEntry().
  2. Guard copy helpers with a boolean 'entryOpen' flag (set true on putNextEntry, false on closeEntry) and skip/log writes when it is false.
  3. If this appears after finish(), the stream is done — create a new TinkerZipOutputStream instead of reusing it.

Example fix

// before
zos.write(buffer, 0, n); // before any putNextEntry
TinkerZipEntry e = new TinkerZipEntry("a.txt");
zos.putNextEntry(e);

// after
TinkerZipEntry e = new TinkerZipEntry("a.txt");
zos.putNextEntry(e);
zos.write(buffer, 0, n);
zos.closeEntry();
Defensive patterns

Strategy: validation

Validate before calling

// maintain explicit entry-open state in copy loops
class ZipCopier {
    private boolean entryOpen = false;
    void open(TinkerZipOutputStream zos, TinkerZipEntry e) throws IOException {
        zos.putNextEntry(e); entryOpen = true;
    }
    void write(TinkerZipOutputStream zos, byte[] b, int n) throws IOException {
        if (!entryOpen) throw new IllegalStateException("write outside entry");
        zos.write(b, 0, n);
    }
    void close(TinkerZipOutputStream zos) throws IOException {
        if (entryOpen) { zos.closeEntry(); entryOpen = false; }
    }
}

Prevention

When it happens

Trigger: Calling write() before the first putNextEntry(); calling write() after closeEntry() for the previous entry but before putNextEntry() for the next; calling write() after finish() has been reached (finish closes the current entry, clearing currentEntry).

Common situations: Copy loops where the source stream is exhausted or the loop body reordered so write happens outside the entry bracket; error paths that call closeEntry() then continue flushing remaining buffers; writing after close().

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/f12c5da39f39d40f. Report an issue: GitHub.