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
- SSH in as the configured user and verify write+mkdir on basePath: sftp> cd /upload; mkdir test; put file
- Fix remote permissions (chmod/chown) or adjust basePath to a writable directory
- Check remote disk space and quotas (df -h)
- 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
- Grant the SFTP user write+mkdir on basePath (chroot jail must allow it)
- Run a test upload when saving the file config in the admin UI
- Watch remote disk/quota; alert before it fills
- Retry once on JschRuntimeException — dropped channels are common over long idle
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.