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

  1. Treat as terminal: cancel the approval and recreate it against the current spec
  2. Keep path templates stable, or drain pending approvals before merging breaking route changes
  3. Add tests that rebuild paths for pending approvals during spec-change CI
  4. 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

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


AI-assisted analysis of jumpserver/jumpserver@6ec464fabd (2026-08-28). Data as JSON: /api/errors/e379a6664152136a. Report an issue: GitHub.