{"record":{"id":"9a83a0076a16f649","repo":"can1357/oh-my-pi","slug":"protocol-touppercase-upload-command-exited-wi","errorCode":null,"errorMessage":"${protocol.toUpperCase()} upload command exited with code ${exitCode}: ${stderr.trim().slice(-300)}","messagePattern":"(.+?) upload command exited with code (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts","lineNumber":255,"sourceCode":"\t\t\t\t\"--ftp-create-dirs\",\n\t\t\t\t\"--upload-file\",\n\t\t\t\t\"-\",\n\t\t\t\t\"--user\",\n\t\t\t\t`${username}:${password}`,\n\t\t\t];\n\t\t\tif (protocol === \"ftps\" && port !== 990) args.push(\"--ssl-reqd\");\n\t\t\targs.push(ftpUploadUrl(protocol, host, port, destinationPath));\n\t\t\ttry {\n\t\t\t\tconst process = Bun.spawn(args, {\n\t\t\t\t\tstdin: request.bytes,\n\t\t\t\t\tstdout: \"ignore\",\n\t\t\t\t\tstderr: \"pipe\",\n\t\t\t\t\tcwd: getSafeProjectCwd(),\n\t\t\t\t});\n\t\t\t\tconst stderr = await new Response(process.stderr as ReadableStream<Uint8Array>).text();\n\t\t\t\tconst exitCode = await process.exited;\n\t\t\t\tif (exitCode !== 0) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`${protocol.toUpperCase()} upload command exited with code ${exitCode}: ${stderr.trim().slice(-300)}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (errorCode(error) === \"ENOENT\") {\n\t\t\t\t\tthrow new DestinationUnavailableError(\"ftp\", \"the configured command binary does not exist\");\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn publication(\"ftp\", request, publicUrl(publicBase, directory, filename));\n\t\t},\n\t};\n}\n\nfunction createSharedFolderUploader(config: DestinationRuntimeConfig): BlobUploader {\n\tconst root = path.resolve(requiredStringOption(config, \"root\"));\n\tconst directory = optionString(config, \"path\");\n\tconst publicBase = requiredStringOption(config, \"publicBaseUrl\");","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts#L237-L273","documentation":"Thrown from the ftp/ftps uploader's upload() when the spawned curl commandBinary finishes with a non-zero exit code. The message embeds the protocol, exit code, and the last 300 characters of curl's stderr so the operator can see the actual FTP failure (login refused, host unreachable, TLS error, etc.).","triggerScenarios":"Any curl invocation during an ftp/ftps upload that exits non-zero: wrong credentials (curl exit 67), couldn't resolve host (6), connection refused (7), SSL/TLS handshake failure for ftps (35), permission to create dirs denied, or remote path invalid.","commonSituations":"Misconfigured FTP credentials; firewall/NAT blocking passive FTP; ftps server requiring explicit TLS on port 21 (uploader adds --ssl-reqd automatically when port != 990, but server may not support it); curl not compiled with FTPS support; read-only remote directory.","solutions":["Read the appended stderr tail in the message — it names the concrete curl failure; match it against curl's exit codes.","Fix credentials: ensure options username/password (via credentials) match the FTP account, and the account may write to the target path.","For ftps, confirm the server's TLS mode: implicit TLS uses port 990, explicit TLS needs --ssl-reqd (already added when port != 990); adjust options.port accordingly.","Test the same transfer manually: cat file | curl --fail --silent --show-error --ftp-create-dirs --upload-file - --user user:pass ftp://host/path to reproduce and debug.","Check network/firewall (passive port range) and DNS if stderr shows connection or resolution errors."],"exampleFix":"// before (manual repro showing exit 67 login refused)\ncurl --upload-file - --user u:wrongpass ftp://ftp.example.com/dir/file.txt\n// after\ncurl --upload-file - --user u:correctpass ftp://ftp.example.com/dir/file.txt  # exit 0, then rerun the upload","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await uploader.upload(request);\n} catch (err) {\n  if (err instanceof Error && /upload command exited with code/.test(err.message)) {\n    const m = err.message.match(/code (\\d+):/);\n    const curlCode = m ? Number(m[1]) : null;\n    // map curlCode (6=DNS, 7=conn refused, 35=TLS, 67=login denied) to actionable remediation\n    if (curlCode !== 67) retryWithBackoff(); // transient network failures only\n  } else throw err;\n}","preventionTips":["Pre-flight the FTP server: verify credentials and a test upload with the same curl flags in CI.","Match options.port and protocol to the server's TLS mode (implicit 990 vs explicit 21 with --ssl-reqd).","Ensure the FTP account has write access and passive-mode port ranges are open in firewalls.","Keep the stderr tail from the error message — it usually names the exact server-side failure."],"tags":["ftp","curl","network","process-exit-code"],"backgroundTag":"process-exited-nonzero","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}