halo-dev/halo · error · IllegalArgumentException
unable to determine kind and namespace from url, %s
Error message
unable to determine kind and namespace from url, %s
What it means
RequestInfoFactory.newRequestInfo parses Halo's Kubernetes-style API paths for the authorization layer. After consuming the API prefix and the API version segment, if the next segment is a 'special verb' ('proxy' or 'watch') but fewer than 2 segments remain (i.e. the verb has no resource after it), the URL is ambiguous and an IllegalArgumentException is thrown. The error message includes the full request path.
Source
Thrown at application/src/main/java/run/halo/app/security/authorization/RequestInfoFactory.java:126
if (!grouplessApiPrefixes.contains(requestInfo.apiPrefix)) {
// one part (APIPrefix) has already been consumed, so this is actually "do we have
// four parts?"
if (currentParts.length < 3) {
// return a non-resource request
return requestInfo;
}
requestInfo.apiGroup = StringUtils.defaultString(currentParts[0]);
currentParts = Arrays.copyOfRange(currentParts, 1, currentParts.length);
}
requestInfo.isResourceRequest = true;
requestInfo.apiVersion = currentParts[0];
currentParts = Arrays.copyOfRange(currentParts, 1, currentParts.length);
// handle input of form /{specialVerb}/*
Set<String> specialVerbs = Set.of("proxy", "watch");
if (specialVerbs.contains(currentParts[0])) {
if (currentParts.length < 2) {
throw new IllegalArgumentException(
String.format("unable to determine kind and namespace from url, %s", request.getPath()));
}
requestInfo.verb = currentParts[0];
currentParts = Arrays.copyOfRange(currentParts, 1, currentParts.length);
} else {
requestInfo.verb = switch (request.getMethod().name().toUpperCase()) {
case "POST" -> "create";
case "GET", "HEAD" -> "get";
case "PUT" -> "update";
case "PATCH" -> "patch";
case "DELETE" -> "delete";
default -> "";
};
}
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative
// to kind
Set<String> namespaceSubresources = Set.of("status", "finalize");
if (Objects.equals(currentParts[0], "namespaces")) {View on GitHub (pinned to d2f5165f9c)
Solutions
- Inspect the full path in the error message and add the missing resource segment after the special verb (e.g. /api/v1/watch/{resource}).
- Fix the reverse-proxy rewrite rule that is truncating the path.
- If the request is genuinely non-resource, route it outside the /api or /apis prefix so RequestInfoFactory returns a non-resource request instead of throwing.
- Audit the caller building the watch/proxy URL to include the resource path.
Example fix
// before: watch with no resource -> IllegalArgumentException // GET /api/v1/watch // after: include the resource being watched // GET /api/v1/watch/posts
Defensive patterns
Strategy: validation
Validate before calling
// Validate the API path before handing it to RequestInfoFactory:
String[] parts = path.replaceAll("^/+|/$", "").split("/");
// after stripping api prefix + (group) + version, if next is proxy/watch, a resource MUST follow
int i = 0;
if (parts.length > i && Set.of("api","apis").contains(parts[i])) i++;
if (parts.length > i && !Set.of("api").contains(parts[i-1]) /*grouped*/) i++;
if (parts.length > i) i++; // version
if (parts.length > i && Set.of("proxy","watch").contains(parts[i]) && parts.length <= i + 1) {
throw new IllegalArgumentException("Special verb '" + parts[i] + "' requires a resource segment");
} Try / catch
try {
RequestInfo info = RequestInfoFactory.INSTANCE.newRequestInfo(request);
} catch (IllegalArgumentException e) {
log.warn("Rejected malformed API path {}: {}", request.getPath(), e.getMessage());
return ServerResponse.badRequest().bodyValue(Map.of("message", e.getMessage()));
} Prevention
- Always include a resource segment after watch/proxy verbs in API URLs.
- Unit-test reverse-proxy rewrite rules against known API paths.
- Keep non-resource endpoints outside the /api and /apis prefixes.
- Add integration tests for the authorization dispatcher covering special-verb paths.
When it happens
Trigger: An API request whose path resolves to '/api/{version}/watch' or '/api/{version}/proxy' (or the grouped '/apis/{group}/{version}/watch') with NO resource segment following the special verb, e.g. GET /api/v1/watch. Triggered inside the authorization dispatcher when classifying an incoming API request.
Common situations: A misconfigured reverse-proxy rewrite that drops the trailing path segments; a buggy custom route/extension issuing watch/proxy requests; a client SDK constructing a watch URL without a resource. This is an internal routing/programming defect rather than something end-users hit through the UI.
Related errors
- You have no permission to delete this notification.
- ESM UI provider output must expose a default PluginModule ex
- ESM UI provider output must contain at most one entry styles
- Cannot resolve shared dependency ${root} from ${resolutionBa
- Shared dependency ${root} resolved to ${packageJson.name ||
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/9a090fd68b3219c0.
Report an issue: GitHub.