theonedev/onedev · error · ClientException
405
Error message
405
What it means
The PyPI pack handler received a non-POST request on the upload endpoint. Only POST (multipart upload) is allowed there; any other method throws ClientException(SC_METHOD_NOT_ALLOWED), which the client sees as HTTP 405.
Source
Thrown at server-plugin/server-plugin-pack-pypi/src/main/java/io/onedev/server/plugin/pack/pypi/PypiPackHandler.java:191
}
data.getSha256BlobHashes().put(fileName, sha256Hash);
var packBlobs = data.getSha256BlobHashes().values().stream()
.map(hash -> packBlobService.findBySha256Hash(projectId, hash))
.filter(Objects::nonNull)
.collect(toList());
packService.createOrUpdate(pack, packBlobs, data.getSha256BlobHashes().size() == 1);
}));
response.setStatus(SC_OK);
break;
}
}
}
} catch (IOException | FileUploadException e) {
throw new RuntimeException(e);
}
} else {
throw new ClientException(SC_METHOD_NOT_ALLOWED);
}
} else {
if (!isGet)
throw new ClientException(SC_METHOD_NOT_ALLOWED);
var currentSegment = pathSegments.get(0);
pathSegments = pathSegments.subList(1, pathSegments.size());
// https://peps.python.org/pep-0503/
if (currentSegment.equals("simple")) {
if (pathSegments.isEmpty()) {
sessionService.run(() -> {
var project = checkProject(projectId, false);
var names = packService.queryNames(project, TYPE, null, true, 0, MAX_VALUE);
var bindings = new HashMap<String, Object>();
bindings.put("names", names);
try {
URL tplUrl = Resources.getResource(getClass(), "packages.tpl");
String template = Resources.toString(tplUrl, UTF_8);View on GitHub (pinned to d44925c47c)
Solutions
- Use POST with multipart/form-data (or the standard twine upload command) against the upload endpoint
- Switch the request method to POST in the script or curl command (curl -X POST --form ...)
- Point read-only checks at the /simple index endpoint instead, which accepts GET
- Check any reverse-proxy rewrite rules that might change the HTTP method
Example fix
// before curl -X PUT https://onedev.example.com/myproj/~pypi/upload -F file=mypkg.whl // after curl -X POST https://onedev.example.com/myproj/~pypi/upload -F "content=@mypkg.whl"
Defensive patterns
Strategy: validation
Validate before calling
// ensure the upload request uses POST
if (httpRequest.method !== 'POST') {
throw new Error('PyPI upload endpoint requires POST, got ' + httpRequest.method);
} Try / catch
try {
await upload(url, formData);
} catch (e) {
if (e.status === 405) {
console.error('Wrong HTTP method for the PyPI endpoint; use POST for upload, GET for /simple', e);
} else { throw e; }
} Prevention
- Use twine for publishing rather than hand-rolled HTTP calls
- Keep upload and index URLs distinct in scripts
- Automate method checks in CI before publishing
When it happens
Trigger: Issuing GET/PUT/DELETE against the project's ~pypi upload URL while the request is not a GET handled by the simple/index branch — i.e. wrong HTTP verb used for the upload route (PypiPackHandler.java:190-191).
Common situations: curl-ing the upload URL to 'test' it with GET; a misconfigured CI script using PUT instead of twine's POST; a health-check probe hitting the upload endpoint with HEAD/GET.
Related errors
- Method Not Allowed
- Method not allowed
- 405
- Package name param is missing
- Package version param is missing
AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06).
Data as JSON: /api/errors/5bc943b2eac14f5b.
Report an issue: GitHub.