dapr/dapr · error
remote actor moved
Error message
remote actor moved
What it means
GenerateJWT persists the signing key by marshalling it to PKCS8 (x509.MarshalPKCS8PrivateKey) and PEM-encoding it. MarshalPKCS8 supports RSA, ECDSA, Ed25519, and X25519 keys; any other crypto.Signer type — notably custom signers or unsupported algorithms — yields 'failed to marshal JWT signing key'. As above, the in-repo caller passes a generated RSA key, so this guards against unsupported key types from external callers.
Source
Thrown at pkg/actors/router/router.go:200
func (r *router) callReminder(ctx context.Context, req *api.Reminder) error {
lar, cctx, cancel, err := r.placement.LookupActor(ctx, &api.LookupActorRequest{
ActorType: req.ActorType,
ActorID: req.ActorID,
})
if err != nil {
return err
}
if req.SkipLock || !lar.Local {
cancel(nil)
} else {
defer cancel(nil)
ctx = cctx
}
if !lar.Local {
if req.IsRemote {
return backoff.Permanent(errors.New("remote actor moved"))
}
err = r.callRemoteActorReminder(ctx, lar, req)
status, ok := status.FromError(err)
if ok && status.Code() == codes.Unavailable {
return backoff.Permanent(err)
}
return err
}
target, err := r.table.GetOrCreate(req.ActorType, req.ActorID)
if err != nil {
return backoff.Permanent(err)
}
if req.IsTimer {
err = target.InvokeTimer(ctx, req)
} else {View on GitHub (pinned to 74ad417027)
Solutions
- Use one of RSA/ECDSA/Ed25519 (or X25519) private keys as the JWT root key.
- If you need another algorithm, serialize it yourself rather than via GenerateJWT.
- Check the wrapped error to confirm the type rejection.
- Keep the stored jwt.key as PKCS8 PEM so future loads round-trip.
Example fix
// before: custom signer
type mySigner struct{ crypto.Signer }
bundle.GenerateJWT(bundle.OptionsJWT{JWTRootKey: mySigner{...}})
// after
rsaKey, _ := rsa.GenerateKey(rand.Reader, 2048)
bundle.GenerateJWT(bundle.OptionsJWT{JWTRootKey: rsaKey}) Defensive patterns
Strategy: type-guard
Validate before calling
switch opts.JWTRootKey.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
default:
return fmt.Errorf("key type %T cannot be PKCS8-marshaled by GenerateJWT", opts.JWTRootKey)
} Type guard
func isPKCS8Marshalable(k any) bool {
switch k.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, x25519.PrivateKey:
return true
}
return false
} Try / catch
j, err := bundle.GenerateJWT(opts)
if err != nil && strings.Contains(err.Error(), "failed to marshal JWT signing key") {
// swap in a supported key type; custom Signers cannot be persisted here
} Prevention
- Restrict JWT root keys to the four PKCS8-capable types.
- For HSM/KMS signers, bypass GenerateJWT and manage persistence externally.
- Round-trip test: marshal then ParsePKCS8PrivateKey in CI.
When it happens
Trigger: OptionsJWT.JWTRootKey is a key type outside PKCS8's supported set (e.g., a DSA key or a wrapper Signer that is not one of the four supported concrete types).
Common situations: Forks adding algorithms like ML-DSA or custom HSM-backed signers; test harnesses passing mock signers.
Related errors
- operations on actor reminders are only possible on hosted ac
- jwt-issuer cannot be set when jwt-enabled is false
- actor host disconnected before responding
- no placement addresses provided
- token validation failed: %w
AI-assisted analysis of dapr/dapr@74ad417027 (2026-08-16).
Data as JSON: /api/errors/1cf9c0d26704064c.
Report an issue: GitHub.