YunaiV/yudao-cloud · error · JschRuntimeException

上传文件到目标目录 ({}) 失败

Error message

上传文件到目标目录 ({}) 失败

What it means

SftpFileClient.upload throws hutool JschRuntimeException after sftp.mkDirs(dir) plus sftp.upload(filePath, tempFile) return false. The client already writes the payload to a local temp file and creates parent dirs, so failure means the SSH/SFTP channel rejected the write: no permission on the remote dir, wrong basePath, disk/quota exceeded, or a channel that went stale despite reconnectIfTimeout().

Source

Thrown at yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java:63

        FtpConfig ftpConfig = new FtpConfig(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
                CharsetUtil.CHARSET_UTF_8, null, null);
        ftpConfig.setConnectionTimeout(CONNECTION_TIMEOUT);
        ftpConfig.setSoTimeout(SO_TIMEOUT);
        this.sftp = new Sftp(ftpConfig);
    }

    @Override
    public String upload(byte[] content, String path, String type) {
        // 执行写入
        String filePath = getFilePath(path);
        String fileName = FileUtil.getName(filePath);
        String dir = StrUtil.removeSuffix(filePath, fileName);
        File file = FileUtils.createTempFile(content);
        reconnectIfTimeout();
        sftp.mkDirs(dir); // 需要创建父目录,不然会报错
        boolean success = sftp.upload(filePath, file);
        if (!success) {
            throw new JschRuntimeException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
        }
        // 拼接返回路径
        return super.formatFileUrl(config.getDomain(), path);
    }

    @Override
    public void delete(String path) {
        String filePath = getFilePath(path);
        reconnectIfTimeout();
        sftp.delFile(filePath);
    }

    @Override
    public byte[] getContent(String path) {
        String filePath = getFilePath(path);
        File destFile = FileUtils.createTempFile();
        reconnectIfTimeout();
        sftp.download(filePath, destFile);

View on GitHub (pinned to 477be9dd49)

Solutions

  1. SSH in as the configured user and verify write+mkdir on basePath: sftp> cd /upload; mkdir test; put file
  2. Fix remote permissions (chmod/chown) or adjust basePath to a writable directory
  3. Check remote disk space and quotas (df -h)
  4. Re-validate the whole config (host, port, username, auth, basePath) from the infra file config page

Example fix

# before: basePath not writable by sftp user
basePath: /srv/sftp/data   # owned by root, 755

# after
sudo chown sftpuser:sftpuser /srv/sftp/data && sudo chmod 755 /srv/sftp/data
# or set basePath to a dir the user owns, then retry upload
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: verify sftp write access
try (Sftp probe = new Sftp(host, port, user, pass)) {
    probe.mkDirs(basePath);
    probe.upload(basePath + "/.probe", new ByteArrayInputStream(new byte[1]));
    probe.delFile(basePath + "/.probe");
} catch (Exception e) {
    throw new IllegalStateException("SFTP 目录不可写: " + basePath, e);
}

Try / catch

try {
    return sftpFileClient.upload(content, path, type);
} catch (JschRuntimeException e) {
    log.warn("SFTP upload failed for {}, retrying once", path, e);
    return sftpFileClient.upload(content, path, type); // stale-channel failures often clear on reconnect
}

Prevention

When it happens

Trigger: Uploading through an SFTP file config whose user lacks write permission on basePath; remote directory quota full; basePath pointing to a read-only mount; key-based auth partially working (login ok, write denied by chroot/AllowChown); stale channel after long idle.

Common situations: SFTP chroot jail not permitting mkdir/write; wrong home-relative basePath after server reconfiguration; disk full on the SFTP host; permission bits 0555 on the target dir.

Related errors


AI-assisted analysis of YunaiV/yudao-cloud@477be9dd49 (2026-08-14). Data as JSON: /api/errors/dbf9cd002bc50356. Report an issue: GitHub.