stablyai/orca · error · Error
agent_session_identity_required
Error message
agent_session_identity_required
What it means
When createOrAttach carries an agentSessionEnsure payload, both its 'claim' and 'surface' fields must pass strict shape validation (isAgentSessionExecutionClaim and isAgentSessionSurfaceBinding). The claim requires a correct digestVersion, base64url digests of exact length, and a resumable agent; the surface requires a valid tabId, leafId, and a terminalHandle prefixed 'term_'. If either fails, the daemon refuses to bind an agent session identity, since a malformed claim cannot be trusted for session ownership.
Source
Thrown at src/main/daemon/daemon-server.ts:999
// too late if a session was accepted in between — that session is then reachable by
// nobody, and the user sees a terminal that acknowledges input and never runs it.
// Why creation only: an attach reaches a session this daemon already hosts, over a
// connection that already exists. Refusing that would break the drain a retiring daemon
// depends on, and it strands nothing — the session is already here.
if (!attachOnly && this.hasLostEndpointOwnership()) {
this.requestRetirementForLostEndpoint()
throw new Error(DAEMON_ENDPOINT_LOST_MESSAGE)
}
this.createOrAttachInFlight++
let routedSessionId = p.sessionId
let result: Awaited<ReturnType<TerminalHost['createOrAttach']>>
try {
if (
p.agentSessionEnsure !== undefined &&
(!isAgentSessionExecutionClaim(p.agentSessionEnsure.claim) ||
!isAgentSessionSurfaceBinding(p.agentSessionEnsure.surface))
) {
throw new Error('agent_session_identity_required')
}
if (!attachOnly) {
await this.preparePtySpawnUnlessCanceled(p.sessionId, clientId)
}
if (p.historySeed !== undefined && p.historySeedTransferId !== undefined) {
throw new Error('Multiple terminal history seed sources')
}
const historySeedChunks =
p.historySeedTransferId !== undefined
? this.historySeedTransfers.take(clientId, p.historySeedTransferId)
: p.historySeed !== undefined
? [p.historySeed]
: undefined
result = await this.host.createOrAttach({
sessionId: p.sessionId,
cols: p.cols,
rows: p.rows,
cwd: p.cwd,View on GitHub (pinned to 1136503c6a)
Solutions
- Re-derive the agentSessionEnsure on the client using the host's authority helpers so claim and surface match the current protocol version.
- Validate the payload with isAgentSessionExecutionClaim() and isAgentSessionSurfaceBinding() locally before sending createOrAttach.
- If you do not need an agent session claim, omit agentSessionEnsure entirely rather than sending a placeholder.
- On version mismatch, negotiate protocol version in hello and construct the claim in the negotiated shape.
Example fix
// before: send an unchecked agentSessionEnsure
await daemon.rpc('createOrAttach', { ..., agentSessionEnsure: raw })
// after: validate before sending, omit if invalid
import { isAgentSessionExecutionClaim, isAgentSessionSurfaceBinding } from '../../shared/agent-session-host-authority'
if (raw && isAgentSessionExecutionClaim(raw.claim) && isAgentSessionSurfaceBinding(raw.surface)) {
await daemon.rpc('createOrAttach', { ..., agentSessionEnsure: raw })
} else {
await daemon.rpc('createOrAttach', { ... })
} Defensive patterns
Strategy: validation
Validate before calling
import { isAgentSessionExecutionClaim, isAgentSessionSurfaceBinding } from '../../shared/agent-session-host-authority'
function agentSessionEnsureIsValid(e: { claim: unknown; surface: unknown } | undefined): boolean {
if (e === undefined) return true
return isAgentSessionExecutionClaim(e.claim) && isAgentSessionSurfaceBinding(e.surface)
} Type guard
import { isAgentSessionExecutionClaim, isAgentSessionSurfaceBinding, type AgentSessionExecutionClaim, type AgentSessionSurfaceBinding } from '../../shared/agent-session-host-authority'
function isAgentSessionEnsure(v: unknown): v is { claim: AgentSessionExecutionClaim; surface: AgentSessionSurfaceBinding } {
if (typeof v !== 'object' || v === null) return false
const o = v as { claim?: unknown; surface?: unknown }
return isAgentSessionExecutionClaim(o.claim) && isAgentSessionSurfaceBinding(o.surface)
} Try / catch
try {
await daemon.rpc('createOrAttach', { ..., agentSessionEnsure })
} catch (e) {
if (e instanceof Error && e.message === 'agent_session_identity_required') {
// drop the claim and retry without it, or rebuild it correctly
await daemon.rpc('createOrAttach', { ... })
} else { throw e }
} Prevention
- Build agentSessionEnsure with the host authority helpers so claim/surface match the current digest version.
- Validate locally with the is* guards before sending; omit the field entirely if you do not need a claim.
- Re-derive the claim when the protocol version changes (negotiated in hello).
When it happens
Trigger: createOrAttach with p.agentSessionEnsure present where claim.digestVersion is wrong, identityDigest/worktreeScopeDigest are not 43-char base64url SHA-256, keyId contains non-base64url chars, surface.tabId/leafId/terminalHandle do not match their formats, or the agent field is not a recognized resumable TuiAgent.
Common situations: A client sending a stale agentSessionEnsure shape from an older protocol version (digestVersion mismatch); hand-constructed or truncated digest fields; a surface binding whose terminalHandle lacks the 'term_' prefix; cross-version client/host where the claim format changed.
Related errors
- Unexpected listApps response: ${JSON.stringify(result)}
- Invalid reset response from host
- agent_session_ownership_unknown
- Package version is not valid semver: ${baseVersion}
- Adhoc build timestamp is invalid.
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/dcaf47c72a4af267.
Report an issue: GitHub.