astrid-runtime/astrid · error
could not authenticate as principal
Error message
could not authenticate as principal '{caller}' for `astrid mcp serve`: no keypair found (keys/{caller}.key), so the daemon would bind this connection to the no-capability `anonymous` identity. Every tool call would then fail the ingress-trust and capability checks and appear to hang. Refusing to serve the MCP bridge as `anonymous`.
Fix: run `astrid agent create {caller}` to mint its keypair (or back-fill an existing keyless principal's), then retry. To serve unauthenticated on purpose, pass `--principal anonymous` before `mcp serve`. What it means
Before serving the MCP bridge, require_authenticated_unless_anonymous refuses to start when the caller principal is not authenticated and has no keypair on disk (keys/{caller}.key). Without a keypair the daemon would bind the connection to the capability-less `anonymous` identity, causing every tool call to fail ingress-trust/capability checks and appear to hang. The error is deliberately verbose and actionable.
Solutions
- Run `astrid agent create <principal>` to mint the missing keypair, then retry `astrid mcp serve`.
- If the principal already exists keylessly, back-fill its keypair with the agent-create command as instructed.
- If unauthenticated serving is intended, pass `--principal anonymous` explicitly before `mcp serve`.
- Verify the exact principal name and that keys/{principal}.key exists relative to the expected keys directory.
Example fix
// before astrid mcp serve --principal alice # no keys/alice.key // after astrid agent create alice astrid mcp serve --principal alice
Defensive patterns
Strategy: validation
Validate before calling
let caller = principal_name;
let key_path = std::path::Path::new("keys").join(format!("{caller}.key"));
if caller != "anonymous" && !key_path.exists() {
eprintln!("run: astrid agent create {caller}");
std::process::exit(1);
} Type guard
fn can_serve(caller: &str, authenticated: bool) -> bool {
authenticated || caller == "anonymous"
|| std::path::Path::new("keys").join(format!("{caller}.key")).exists()
} Try / catch
if let Err(e) = serve(principal, workspace).await {
if e.to_string().contains("could not authenticate as principal") {
// message already includes the fix command; surface it verbatim to the user
eprintln!("{e}");
}
} Prevention
- Run `astrid agent create <principal>` before serving for any named principal
- Back up and migrate the keys/ directory with workspaces
- Verify principal-name spelling matches an existing keypair
- Use --principal anonymous only deliberately
When it happens
Trigger: Running `astrid mcp serve --principal <name>` (or the library call) where <name> is not anonymous, authenticated=false (no keypair at keys/{name}.key), so serve() invokes this guard and bails.
Common situations: Principal created conceptually but `astrid agent create` never run; keypair deleted or keys/ directory lost; typo in the principal name so no key file matches; migrating machines without copying keys/.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- guard uplink authenticated as anonymous instead of…
- MCP attach registration hook_token is invalid
- MCP attach registration is missing hook_token
- MCP attach registration principal
- MCP gateway control authority is incomplete
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/c7b13f24aeec753f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/mcp/mod.rs:141
/// identity.
///
/// `astrid --principal X mcp serve` connects, but if `X` has no keypair the
/// handshake falls to the legacy single-frame path and the daemon stamps the
/// connection `anonymous`. The bridge would then come up "successfully" yet
/// every `tools/call` fails the ingress-trust and capability checks — to a
/// client it just hangs/times out, with no hint why. This turns that silent,
/// confusing failure into a loud, actionable error at startup.
///
/// Requesting `anonymous` explicitly (`astrid --principal anonymous mcp serve`) is allowed:
/// serving unauthenticated is then a deliberate choice, not an accident.
fn require_authenticated_unless_anonymous(
caller: &astrid_core::PrincipalId,
authenticated: bool,
) -> Result<()> {
if authenticated || *caller == astrid_core::PrincipalId::anonymous() {
return Ok(());
}
anyhow::bail!(
"could not authenticate as principal '{caller}' for `astrid mcp serve`: \
no keypair found (keys/{caller}.key), so the daemon would bind this \
connection to the no-capability `anonymous` identity. Every tool call \
would then fail the ingress-trust and capability checks and appear to \
hang. Refusing to serve the MCP bridge as `anonymous`.\n\n\
Fix: run `astrid agent create {caller}` to mint its keypair (or \
back-fill an existing keyless principal's), then retry. To serve \
unauthenticated on purpose, pass `--principal anonymous` before `mcp serve`."
);
}
/// Explicit `anonymous` MCP is a transport-only, no-capability mode and has no
/// broker capsule to prove. Named principals must always prove their broker
/// front door before stdio is exposed.
fn broker_readiness_required(caller: &astrid_core::PrincipalId) -> bool {
*caller != astrid_core::PrincipalId::anonymous()
}
View on GitHub (pinned to affd8760f4)