Skip to content

Remote mode: first message of a session silently dropped (lastSeq === 0 discards the live message) → permanent hang #1408

Description

@Mo-ZheHan

Summary

In remote mode, the first message sent from the phone is silently dropped and the agent is never invoked — no subprocess, no output, no error. The CLI sits at [MessageQueue2] Waiting for messages... (the app shows "Starting new Claude session…" / "Continuing Claude session…") forever. Every subsequent message is dropped the same way, so the session is permanently stuck until you switch back to local.

The cause is client‑side, in packages/happy-cli/src/api/apiSession.ts, and is present on current main.

Environment

  • happy: 1.1.10-beta.10 (also reproduces on latest 1.1.8; the code path is unchanged on main)
  • Claude Code 2.1.178 · node v20.20.2 · macOS (arm64)

Steps to reproduce

  1. Start a session in remote mode (happy --happy-starting-mode remote, or hand a fresh session off to the phone).
  2. From the phone, send the first message.

→ The message shows as sent on the phone, but the agent never runs and the CLI hangs.

Root cause

The socket new-message handler in apiSession.ts discards the first live message of a session:

// apiSession.ts (main)
if (data.body.t === 'new-message') {
    const messageSeq = data.body.message?.seq;
    if (this.lastSeq === 0) {            // L203
        this.receiveSync.invalidate();   // defer to a REST catch-up…
        return;                          // …and drop the live message we already hold
    }
    if (typeof messageSeq !== 'number' || messageSeq !== this.lastSeq + 1 || data.body.message.content.t !== 'encrypted') {
        this.receiveSync.invalidate();
        return;
    }
    const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
    this.routeIncomingMessage(body);
    this.lastSeq = messageSeq;
}
  • lastSeq starts at 0 (L115) and is only ever advanced by routed messages; it is not seeded from the session's stored seq on load/connect.
  • On connect, the handler calls receiveSync.invalidate()fetchMessages(after_seq = lastSeq = 0). For a brand‑new remote session there are no messages yet, so it routes nothing and lastSeq stays 0.
  • When the first phone message then arrives live (new-message, seq: 1, content.t: 'encrypted' — the full encrypted payload is in hand), the lastSeq === 0 branch discards it and trusts the REST catch‑up. In practice that catch‑up does not deliver the just‑pushed message, so lastSeq never leaves 0 and every later message is dropped by the same branch.
  • In remote mode the run loop is blocked awaiting the message queue ([MessageQueue2] Waiting for messages...), so query() / the agent is never started → a silent, permanent hang.

Evidence (happy 1.1.10-beta.10)

[remote]: Continuing existing session: null
[claudeRemote] Found --resume with session ID: afc27193-…
[MessageQueue2] Waiting for messages...
Socket connected successfully
[SOCKET] [UPDATE] Received update:   # body.t:"new-message", message.seq:1, content.t:"encrypted"
# …nothing routed, no agent spawned, for ~5 minutes — until the user manually switched to local

This was a fresh Happy session resuming a Claude session via --resume <id>; HAPPY_RECONNECT_SESSION_ID was not set, so skipExistingMessages() was not involved (see Related).

Suggested fix

Don't throw away a message that was already received over the socket. Any of:

  1. In the new-message handler, route the live message directly when content.t === 'encrypted' and it is the next expected seq — i.e. treat lastSeq === 0 && seq === 1 as valid instead of returning.
  2. Seed lastSeq from the session's server‑side message seq when the session is loaded, so the gate never fires for the legitimate first message.
  3. Close the REST/live gap so fetchMessages reliably returns a message that was just pushed live.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions