jumpserver/jumpserver · error · PermissionDenied
Approved operation path has changed.
Error message
Approved operation path has changed.
What it means
Raised when rebuilding the request from the stored payload produces a different URL path than the one recorded on the approval. The path is rebuilt via builder.build(operation, request_payload); drift means the operation template or path params changed since creation, so executing would hit a different endpoint.
Source
Thrown at apps/chat_ai/approvals/service.py:129
approval.agent_run.assistant_message.error = 'APPROVAL_EXPIRED'
approval.agent_run.assistant_message.save(update_fields=('status', 'error', 'date_updated'))
raise ValidationError('Approval has expired.')
if not hmac.compare_digest(approval.request_hash, self.request_hash(approval.request_payload)):
raise PermissionDenied('Approval request hash is invalid.')
try:
expected_signature = self.signature_for(approval)
except SigningKeyUnavailable as exc:
raise PermissionDenied('Approval signing key is unavailable.') from exc
if not hmac.compare_digest(approval.signature, expected_signature):
raise PermissionDenied('Approval signature is invalid.')
operation = self.registry.get(approval.operation_id)
if not operation or operation.method != approval.method:
raise PermissionDenied('Approved operation no longer exists.')
self.policy.enforce(operation, approval.request_payload)
path, _, _ = self.builder.build(operation, approval.request_payload)
if path != approval.path:
raise PermissionDenied('Approved operation path has changed.')
approval.status = Approval.Status.PROCESSING
approval.confirmed_by = user
approval.confirmed_at = timezone.now()
approval.expires_at = timezone.now() + timedelta(
seconds=getattr(settings, 'CHAT_AI_API_TIMEOUT', 15) + 60
)
approval.save(update_fields=(
'status', 'confirmed_by', 'confirmed_at', 'expires_at', 'date_updated'
))
return approval, operation
@staticmethod
def finish(approval, result):
ok = bool(result.get('ok'))
approval.status = Approval.Status.CONFIRMED if ok else Approval.Status.FAILED
approval.result_summary = summarize(result)
approval.error = '' if ok else f'Core API returned HTTP {result.get("status_code", 0)}'
approval.save(update_fields=('status', 'result_summary', 'error', 'date_updated'))View on GitHub (pinned to 6ec464fabd)
Solutions
- Treat as terminal: cancel the approval and recreate it against the current spec
- Keep path templates stable, or drain pending approvals before merging breaking route changes
- Add tests that rebuild paths for pending approvals during spec-change CI
- If unexpected, diff the approval.path against builder.build output to find the template drift
Example fix
# before
path, _, _ = builder.build(operation, approval.request_payload)
assert path == approval.path # fails after route rename
# after
# deployment step: drain pending approvals before renaming routes
for a in Approval.objects.filter(status=Approval.Status.PENDING):
ApprovalService.cancel(a.id, a.user, a.org_id)
deploy_new_spec() Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
null
Try / catch
try {
await confirm(approvalId);
} catch (e) {
if (e.status === 403 && /path has changed/i.test(e.message)) {
await cancelApproval(approvalId);
await restartAgentRun(conversationId);
} else throw e;
} Prevention
- Avoid renaming route templates while approvals are pending
- Drain pending approvals as part of deploying breaking route changes
- Test path rebuilding against pending approvals in deploy previews
When it happens
Trigger: Path template or path-param serialization in the registry/builder changed after the approval was created (e.g. '/files/{id}' → '/files/{file_id}', or encoding changes), so the rebuilt path differs.
Common situations: API spec redeployments renaming path params or restructuring routes; builders changing URL encoding of params; approvals pending across such a release.
Related errors
- Approved operation no longer exists.
- This operation does not require approval.
- Approval belongs to another user.
- Approval belongs to another organization.
- Approval is already {approval.status}.
AI-assisted analysis of jumpserver/jumpserver@6ec464fabd (2026-08-28).
Data as JSON: /api/errors/e379a6664152136a.
Report an issue: GitHub.