Skip to content

Auth/control UX: anonymous users silently locked out of plan mode & chat (misleading "another client" message) #54

Description

@pskeshu

Summary

With user accounts configured (the default once auth/users.yaml exists, and GENTLY_NO_AUTH unset), a user who opens the dashboard without signing in connects as Anonymous / view-only. Every chat, command, and cancel they send is silently dropped at the control gate, so plan mode and the agent appear completely unresponsive — you type, nothing happens, no error, no agent reply, no LLM call. There is no obvious in-context cue that you need to sign in and take control first.

This bit a real local-testing session: messages were sent into /plan, the agent never responded, and the only signal was a notification toast that was itself misleading (see Bug 2).

Context: the new view-only-login / control-lock paradigm landed in #43 (UX v2); this issue is about hardening its UX.

Repro

  1. Have D:\Gently3\auth\users.yaml present (accounts configured) and don't set GENTLY_NO_AUTH.
  2. python launch_gently.py (agent enabled).
  3. Open the dashboard without signing in → you connect as Anonymous.
  4. Open agent chat, /plan, type a message.
  5. Expected: the agent engages plan mode, or you're clearly told you must sign in / take control.
    Actual: message is dropped; you get a "Viewing only — control is held by another client" toast even though nobody holds control. No agent response.

Evidence (from a real session)

  • Transcript: 21 frames, 0 agent replies; user chat frames present; 4× "Viewing only — control is held by another client" notifications, each with you_have_control=False, holder=None.
  • Interaction log never created → the message never reached the agent / LLM.

Root cause

gently/ui/web/routes/agent_ws.py:

  • L340 can_control = role in CONTROL_ROLES — Anonymous role is not in CONTROL_ROLES.
  • L658 if _control["holder"] is None and can_control: — control is only auto-granted to a control-capable client, so an Anonymous client never becomes holder.
  • L831–832 the drive gate drops the message:
    if msg_type in ("chat", "command", "cancel") and client_id != _control["holder"]:
        holder_label = _client_labels.get(_control["holder"]) or "another client"
        ... # notify + continue (message dropped)

Bugs / gaps

1. Silent lockout with no actionable path. An anonymous user has no clear way to discover they must sign in. The bounce toast does not link to a sign-in action, and take_control (L806) just says "Your account can watch but not control" — a dead end without a sign-in affordance surfaced at that moment.

2. Misleading "another client" message. When holder is None (nobody holds control), L832 falls back to "another client", so the toast claims "control is held by another client" — false. It should distinguish:

  • nobody holds control + you can't control → "Sign in to take control."
  • nobody holds control + you can → auto-grant or "Click Take control."
  • someone else holds it → name them.

3. (Related, minor) WebSocket reconnect race. gently/ui/web/routes/websocket.py:97 logs WebSocket error: Cannot call "send" once a close message has been sent. on fast disconnect/reconnect — the send isn't guarded by a connection-state check. Benign (auto-reconnects) but noisy.

Suggested improvements

  • Surface control/identity state prominently: a persistent header chip — Viewing only · Sign in to control vs In control — instead of relying on transient toasts.
  • When an anonymous/viewer user attempts to drive, return a notification that includes a sign-in action (and, post sign-in, auto-grant control if free).
  • Fix the holder is None labeling so the message reflects reality (Bug 2).
  • Consider an explicit "request/take control" affordance that's discoverable before the first dropped message.
  • Guard the viz-socket send against a closed connection (Bug 3).

Workaround

GENTLY_NO_AUTH=1 (env or project-root .env) disables accounts so localhost auto-holds control — fine for local/dev, but not a fix for the multi-user UX.

Environment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions