Leantime/leantime · warning · Exception

notification.plugin_zip_seek_err

Error message

notification.plugin_zip_seek_err

What it means

The match maps ZipArchive::ER_SEEK to 'Zip: Seek error'. libzip could not seek within the temp file while locating the central directory (it seeks backward from EOF). A regular file on a healthy local filesystem always supports this, so the error points at an exotic storage layer for /tmp - certain FUSE, network, or overlay mounts - or a file changed under libzip between operations.

Source

Thrown at app/Domain/Plugins/Services/Plugins.php:743

            throw new \Exception(__('notification.plugin_cant_remove'));
        }

        if (! mkdir($pluginDir) && ! is_dir($pluginDir)) {
            throw new \RuntimeException(sprintf('Directory "%s" was not created', $pluginDir));
        }

        $zip = new \ZipArchive;

        match ($zip->open($temporaryFile)) {
            \ZipArchive::ER_EXISTS => throw new \Exception(__('notification.plugin_zip_exists')),
            \ZipArchive::ER_INCONS => throw new \Exception(__('notification.plugin_zip_inconsistent')),
            \ZipArchive::ER_INVAL => throw new \Exception(__('notification.plugin_zip_invalid_arg')),
            \ZipArchive::ER_MEMORY => throw new \Exception(__('notification.plugin_zip_malloc')),
            \ZipArchive::ER_NOENT => throw new \Exception(__('notification.plugin_zip_no_file')),
            \ZipArchive::ER_NOZIP => throw new \Exception(__('notification.plugin_zip_not_zip')),
            \ZipArchive::ER_OPEN => throw new \Exception(__('notification.plugin_zip_cant_open')),
            \ZipArchive::ER_READ => throw new \Exception(__('notification.plugin_zip_read_err')),
            \ZipArchive::ER_SEEK => throw new \Exception(__('notification.plugin_zip_seek_err')),
            default => throw new \Exception(__('notification.plugin_zip_unknown_err')),
            true => null,
        };

        if (! $zip->extractTo($pluginDir)) {
            throw new \Exception(__('notification.plugin_zip_cant_extract'));
        }

        $zip->close();

        unlink($temporaryFile);

        // read the composer.json content from the plugin phar file
        $pluginModel = $this->createPluginFromComposer($foldername, $plugin->license);

        if (! $this->pluginRepository->addPlugin($pluginModel)) {
            throw new \Exception(__('notification_cant_add_to_db'));
        }

View on GitHub (pinned to 9a9f49f100)

Solutions

  1. Point sys_temp_dir at a local ext4/xfs volume (php.ini sys_temp_dir, or TMPDIR for the php-fpm service) and retry.
  2. Check what /tmp actually is: mount | grep ' /tmp ' - replace network/overlay mounts with local storage.
  3. Ensure only one install of the same plugin runs at a time.
  4. Retry on a different host to confirm it is storage-specific.
Defensive patterns

Strategy: fallback

Validate before calling

// ensure the temp dir sits on a seekable local filesystem
$dir = sys_get_temp_dir();
if (str_contains(realpath($dir) ?: '', '/nfs') || is_link($dir)) {
    $dir = '/var/tmp'; // local fallback
    if (! is_writable($dir)) {
        throw new RuntimeException('No local seekable temp dir available');
    }
}

Prevention

When it happens

Trigger: installMarketplacePlugin() with sys_get_temp_dir() resolving to an NFS/overlay/FUSE mount whose seek/mmap semantics are broken; the temp file being modified concurrently (another process appending/truncating) while ZipArchive parses it.

Common situations: /tmp mounted over network storage in virtualized hosting; exotic container storage drivers; concurrent duplicate installs racing on the same temp filename.

Related errors


AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21). Data as JSON: /api/errors/6dfbf144defc01cc. Report an issue: GitHub.