Skip to content

Linux is broken: daemon starts as root, but interacting with user browser requires user account #52

Description

@christian-posta

For both controller start (when the daemon tries to direct the user to the browser for OIDC login) and for apiKeyHelper ... there are permissions issues that are blocking things from working correctly in Linux. The issue makes the process (daemon/apiKeyHelper) hang indefinitely and cannot proceed.

Activity

  1. christian-posta commented on Aug 27, 2026

    @christian-posta
    CollaboratorAuthor

    Logging codex's analysis here to come back to it (or if someone wants to dig in):

      1. The root daemon successfully binds 127.0.0.1:15555.
      2. It prints the usable prompt URL.
      3. It invokes xdg-open as root.
      4. xdg-open exits with status 3, encoded here as unix_wait_status(768).
      5. The daemon keeps waiting for the callback.
    
      The important mismatch is visible in adjacent lines:
    
      local API access granted to sudo user authorized_uid=1000
      ...
      could not open the browser automatically
    
      AgentDesktop recognizes UID 1000 only when assigning the Unix-socket permissions at crates/agent/src/daemon.rs:661. That user identity is not used by the browser launch at
      crates/agent/src/oidc.rs:478.
    
      For this current run, opening http://127.0.0.1:15555/ in the Ubuntu user’s browser should complete enrollment, provided it happens within the ten-minute callback timeout.
    
      My product recommendation is now more targeted:
    
      - Controller enrollment already exposes its authorization state to the desktop app, and the desktop app already knows how to open it as the logged-in user at crates/
        agentdesktop/src/main.rs:581. Make that the authoritative browser-launch path.
    
      - When the daemon is root or otherwise lacks an interactive desktop session, it should not call xdg-open; it should log the manual URL and wait for the desktop UI.
      - Give gateway and Anthropic subscription authorization the same status/UI handoff, since they currently lack the enrollment flow’s integration.
      - Keep manual URL output for headless and CLI installations.
      - Avoid trying to reconstruct the desktop session with sudo -u, DISPLAY, and D-Bus variables. That becomes unreliable with multiple sessions, SSH, containers, and Wayland.
    
      So there are three separate findings:
    
      - Umask bug: managed file/helper permissions.
      - Shared browser bug: privileged daemon calls the browser launcher.
      - Subscription-specific blocking: credential helpers can wait behind an unfinished interactive flow without a client timeout.
    
      They can appear together on root-run Linux, but should be fixed independently.
    
  2. christian-posta commented on Aug 27, 2026

    @christian-posta
    CollaboratorAuthor

    Note, manually going to the browser to the right URLs when things get stuck (http://localhost:15555 and http://locahost:51327, respectively) allow me to complete the login. So it's not that the flow is broken, just the automated helper to open/direct the browser is broken.

  3. howardjohn commented on Aug 27, 2026

    @howardjohn
    Contributor

    with the UI we should probably be able to open it from the UI which runs in non-root. If you just run the daemon alone though then yeah need to directly visit. it does log the url

  4. devmittal02 commented on Sep 8, 2026

    @devmittal02
    Contributor

    Confirmed SSH tunnel workaround for headless Linux (EC2 / servers)

    Building on @christian-posta's note that manually visiting the URLs works — here is a procedure that makes this reliable for headless servers with no display at all.

    The daemon logs the enrollment URL to stdout and the OIDC callback server listens on port 51327 (the redirect URI). SSH port-forwarding carries the Okta callback from the operator's browser back to the daemon without any display on the server.

    Setup (one-time per machine):

    # 1. On your laptop — open the tunnel BEFORE starting the daemon so the
    #    callback port is ready when Okta redirects.
    ssh -L 51327:127.0.0.1:51327 ubuntu@<server-ip>
    
    # 2. On the server — run as the ubuntu user (not root) to avoid the xdg-open
    #    root issue. --user keeps state in ~/.local/share/agentdesktop.
    agentdesktop daemon \
      --config /etc/agentdesktop/config.yaml \
      --user \
      --state-dir ~/.local/share/agentdesktop

    The daemon prints a URL (something like ) to stdout. Open that URL in the browser on your laptop (not the server). Log in with Okta. Okta redirects to — your browser follows it to your laptop, the SSH tunnel carries it to the daemon on the server. Enrollment completes within the 10-minute window.

    After the initial enrollment the daemon connects to the controller without any further browser interaction, so you can drop the tunnel and the service runs unattended.


    Longer term — device code flow would remove the tunnel requirement entirely.

    Your Okta org already has in . The device code flow lets a headless process print a short code + URL, the operator opens the URL on any device, approves it, and the daemon polls for completion — no port forwarding, no display, no race against a 10-minute window. This is what (our internal Okta credential helper) uses on the same Okta org for exactly this reason.

    A flag on (auto-selected when / are absent, or when fails) would make Linux server enrollment a one-liner. Happy to test against our Okta setup if it would help.

  5. devmittal02 commented on Oct 4, 2026

    @devmittal02
    Contributor

    Follow-up on headless Linux. Two PRs together let a daemon running on a server or cloud VM finish both browser-based steps without a local browser or an SSH tunnel:

    Both are opt-in, and I tested both end to end on Ubuntu 24.04 against a real IdP. #145 is stacked on #144.

    @peterj could you review these when you have a moment?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions