Troubleshooting
Troubleshooting
DNS Discovery
"No IdP found for domain"
Symptom: Login fails with "Could not discover IdP for this email domain."
Cause: Missing or misconfigured _ddisa TXT record.
Fix:
# Check if the record exists
dig _ddisa.example.com TXT +short
# Expected output:
# "v=ddisa1 idp=https://id.example.com; mode=open"
If no record is returned:
- Add the TXT record to your DNS provider
- Wait for propagation (check at dnschecker.org)
- Verify the format:
v=ddisa1 idp=https://...; mode=open
If the SP has a fallbackIdpUrl configured, users from domains without DDISA records will be redirected there.
Passkey Registration
"Registration failed: origin mismatch"
Symptom: Passkey registration fails immediately.
Cause: The rpOrigin in the IdP config doesn't match the browser's origin.
Fix: Ensure rpOrigin exactly matches the URL in the browser address bar:
// ❌ Wrong
rpOrigin: 'https://id.example.com/' // trailing slash
rpOrigin: 'http://id.example.com' // wrong protocol
// ✅ Correct
rpOrigin: 'https://id.example.com'
For local development: rpOrigin: 'http://localhost:3000'
"Registration failed: rpID mismatch"
Symptom: Passkey registration fails after biometric prompt.
Cause: The rpID doesn't match the origin's domain.
Fix: rpID must be the domain (without protocol or port):
// ❌ Wrong
rpID: 'https://id.example.com' // includes protocol
rpID: 'id.example.com:3000' // includes port
// ✅ Correct
rpID: 'id.example.com'
rpID: 'localhost' // for development
"Passkeys require HTTPS"
Symptom: Passkey registration fails in production but works locally.
Cause: WebAuthn requires a secure context. localhost is exempt, but production must use HTTPS.
Fix: Ensure your IdP is served over HTTPS with a valid certificate.
OAuth / Login Flow
"OAuth callback error: PKCE mismatch"
Symptom: Login redirects back to SP but fails with a code exchange error.
Cause: The PKCE code_verifier doesn't match the code_challenge sent during authorization.
Fix:
- Clear browser cookies and try again (session state may be corrupted)
- Ensure
sessionSecretis consistent across deployments (don't rotate mid-session) - Check that the SP is not behind a load balancer with inconsistent sticky sessions
"OAuth callback error: audience mismatch"
Symptom: Login fails with "JWT audience does not match client ID."
Cause: The SP's clientId doesn't match what the IdP expects.
Fix:
// SP config — clientId must match the SP's public domain
openapeSp: {
clientId: 'app.example.com' // not 'https://app.example.com'
}
In development, clientId auto-derives to localhost:PORT.
"OAuth callback error: expired authorization code"
Symptom: Login fails after a long delay between IdP auth and SP callback.
Cause: Authorization codes expire after 60 seconds.
Fix: Retry the login. If the problem persists, check for network latency between SP and IdP.
Agent Authentication
"Agent not found"
Symptom: /api/agent/challenge returns 404.
Cause: Agent not enrolled, or enrolled with a different email/ID.
Fix:
# List enrolled agents
curl https://id.example.com/api/admin/agents \
-H "Authorization: Bearer <management-token>"
Verify the agent's email matches what you're sending in agent_id.
"Invalid signature"
Symptom: /api/agent/authenticate returns 401.
Cause: The signature doesn't match the registered public key.
Fix:
- Verify you're signing the exact challenge string (no extra newline or whitespace)
- Ensure you're using the correct private key (matching the enrolled public key)
- Check that the key format is Ed25519 (not RSA or ECDSA)
# Verify key type
ssh-keygen -l -f ~/.ssh/agent_key
# Should show: 256 SHA256:... (ED25519)
"Challenge expired"
Symptom: /api/agent/authenticate returns 401 with "expired challenge."
Cause: Challenges expire after 60 seconds.
Fix: Request a new challenge and authenticate within 60 seconds. Ensure system clocks are synchronized (NTP).
Grants
"Grant already decided"
Symptom: Approving or denying a grant returns 400.
Cause: The grant was already approved, denied, or revoked.
Fix: Check the grant status:
curl https://id.example.com/api/grants/<id> \
-H "Authorization: Bearer <token>"
"Not authorized to approve"
Symptom: Approving a grant returns 403.
Cause: The logged-in user is not the agent's owner, approver, or an admin.
Fix: Check who the agent's owner/approver is:
curl https://id.example.com/api/admin/agents/<agent-id> \
-H "Authorization: Bearer <management-token>"
Update the agent's approver field if needed.
"cmd_hash mismatch" (escapes)
Symptom: escapes exits with code 5 and logs "cmd_hash mismatch."
Cause: The command in the grant request doesn't match the command being executed.
Fix: The command array must match exactly:
# Grant was requested for:
# command: ["systemctl", "restart", "nginx"]
# ❌ This won't match (different arguments)
escapes --grant "$JWT" -- systemctl stop nginx
# ✅ This matches
escapes --grant "$JWT" -- systemctl restart nginx
escapes
"JWT verification failed" (exit code 5)
Symptom: escapes refuses to execute and exits with code 5.
Possible causes and fixes:
| Error in audit log | Cause | Fix |
|---|---|---|
| "issuer not in allowed_issuers" | JWT issuer URL doesn't match config | Add the IdP URL to allowed_issuers in /etc/openape/config.toml |
| "audience mismatch" | JWT aud claim ≠ configured audience | Check allowed_audiences in config (default: ["escapes"]) |
| "target_host mismatch" | JWT target_host ≠ system hostname | Set host in config to match, or fix the grant request |
| "approver not allowed" | JWT decided_by not in allowed list | Add the approver to allowed_approvers in config |
| "cmd_hash mismatch" | Command doesn't match grant | Re-request grant with the exact command |
| "grant already consumed" | once grant was already used | Request a new grant |
Proxy
"All requests blocked"
Symptom: The proxy blocks every request.
Cause: default_action is set to block and no allow rules match.
Fix: Add allow rules for expected traffic:
default_action = "block"
[[allow]]
domain = "api.github.com"
methods = ["GET"]
Or change default_action to request for a more permissive default.
Session Issues
"Session not persisting"
Symptom: User is logged in but loses the session on next request.
Cause: sessionSecret not set or changes between deployments.
Fix:
- Set a stable
sessionSecretenvironment variable - Ensure the secret is the same across all instances (if load-balanced)
- Check cookie settings: domain must match, and
Secureflag requires HTTPS
"Session expired unexpectedly"
Symptom: User is logged out before sessionMaxAge.
Cause: Server restart (in-memory sessions) or cookie domain mismatch.
Fix:
- Configure persistent storage for sessions
- Verify cookie domain matches the app's domain