Skip to content

launch-auth never persists the unix_username claim — strict Unix user mode blocks all impersonation (executor spawn, git state, Codex auth) #2064

Description

@richardfogaca

Summary

launch-auth never reads or persists a unix_username claim from the launch/handoff assertion. On any deployment running unix_user_mode: strict, every user provisioned via launch auth therefore has unix_username = null, and all user impersonation fails — executor spawn, branch git-state auto-population, Codex auth, scheduled sessions.

Reported by @Antonio-Rivero from the dogfood workspace: agent prompts never start, the session just spins.

❌ [Daemon] Executor spawn failed for session=019fa7093f017d72829d5448
   task=019fa7093f5b79faa64da93b agent=codex unix_username=null:
   Strict Unix user mode requires unix_username to be set.

Thrown from resolveUnixUserForImpersonationpackages/core/src/unix/user-manager.ts:430.

Root cause (verified against main @ 0409b302b)

unix_username appears nowhere in apps/agor-daemon/src/auth/launch-auth.tsgrep -i unix on that file returns zero hits. Specifically:

  1. The claim isn't declared. interface LaunchClaims extends JwtPayload (launch-auth.ts:79–94) lists iss, sub, aud, email, email_verified, name, picture, avatar, role, provider, instance_id, runtime_instance_id, jti, nonce. No unix_username.
  2. The existing-user branch doesn't set it. upsertLaunchUser (launch-auth.ts:320) updates only name, role, updated_at, and data (avatar + external identities).
  3. The new-user branch doesn't set it either. The insert sets user_id, email, password, name, emoji, role, timestamps, onboarding_completed, must_change_password, data — and nothing else.

The only code that ever writes users.unix_username is admin-gated:

  • apps/agor-daemon/src/services/users.ts:455 (users-service patch)
  • apps/agor-daemon/src/mcp/tools/users.ts:282,342 (admin MCP tools)

Per apps/agor-daemon/src/register-hooks.ts:2230, unix_username is explicitly excluded from self-service profile updates — so a user cannot set it themselves.

Important correction to the initial hypothesis

The first diagnosis assumed the claim is mapped only at user-record creation, so only records predating the change would lack it. That is not what the code does — the claim is never consumed on either branch. Consequences:

  • Brand-new users are affected too. "Recreate the user" or "new signups will be fine" will not work as a workaround.
  • The fix is different: add the claim to LaunchClaims and set it on both branches, plus backfill existing rows — not merely "also map it on update."

Why nothing surfaces in the UI

The spawn failure leaves the task in a non-terminal state, and the containment that would surface it as startup_timeout never runs, because the executor supervisor is separately broken on multi-tenant runtimes:

[executor-heartbeat] Supervisor check failed: Failed to find orphaned tasks:
  Missing tenant database scope for daemon database access

That is tracked in preset-io/agor-cloud#191 (ADR: tenant-safe executor supervision). Note findOrphaned() throws (packages/core/src/db/repositories/tasks.ts:444) and getOrphaned() is the first await inside checkOnce()'s try block (executor-heartbeat-supervisor.ts:46-48) — so the entire supervisor tick aborts, taking the 5-minute dispatch-connect timeout down with it. A hard failure consequently reads as "still working."

These are two independent bugs. This issue is only the missing claim mapping; the masking is #191's scope.

Expected behavior

A user provisioned or refreshed through launch auth should carry the unix_username supplied by the identity provider, so strict-mode impersonation works without manual admin intervention.

Proposed fix

  1. Add unix_username?: string to LaunchClaims.
  2. Set it in upsertLaunchUser on both the insert and the update branch — update should fill it when currently null (and decide explicitly whether a changed claim should overwrite an existing value).
  3. Respect the existing uniqueness guard (packages/core/src/db/repositories/users.ts:318-322) and surface a clear error on collision rather than failing the launch opaquely.
  4. Backfill existing users whose records were created before the mapping exists.
  5. Fail loudly at launch time when unix_user_mode: strict and no unix_username can be resolved, instead of deferring the failure to first spawn.

This stays deployment-neutral: unix_username is a generic OSS user field and the launch assertion is a generic OSS mechanism, so no Agor Cloud concepts leak into the runtime (per the boundary rule in agor-cloud#191).

Immediate workaround

An admin can set the field directly via the users service or the users_update MCP tool. Requires admin — the affected user cannot self-serve.

Blockers / Dependencies

  • None blocking the claim mapping itself; it is self-contained in launch-auth.ts.
  • Masking dependency: preset-io/agor-cloud#191 — until the supervisor is tenant-safe, failures of this kind stay invisible in the UI. Fixing this issue removes this trigger but not the class.
  • Cross-repo coordination: confirm the Agor Cloud control plane emits unix_username in the assertion under exactly that name. Reported as stamped at launchCodes.ts:358 with consumed launch codes showing "unix_username": "antonio_rivero"not independently verified, since the Cloud control-plane source was not available to me.

Verification notes / caveats

  • Verified against preset-io/agor main @ 0409b302b as deployed on the sandbox host. The affected dogfood runtime may run a different version — worth confirming before relying on the line references.
  • Not reproducible on the sandbox instance: 0 Strict Unix user mode failures, 0 Missing tenant database scope, orphan cleanup working, and unix_user_mode unset in config.yaml. This needs a strict-mode multi-tenant runtime to reproduce.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions