> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Team Chat and Huddle state surprises

> Diagnose Team Chat and huddle states that look broken but are degrade-open semantics: a who's-online rail that empties for a moment, presence that disagrees with messages, a reaction that reappears after a remove, a phantom huddle live badge, and a 409 when joining a huddle that is ending.

# Team Chat and Huddle state surprises

Team Chat (channels, direct messages, reactions, presence) and huddles are
built so that a transient backend fault degrades to a calm, recoverable
state instead of an error screen. Most "Team Chat is broken" reports are one
of those degrades working as designed. This page covers each surprising
state, what causes it, and how to tell a passing degrade from a real fault.

## Team Chat is empty for a moment, then renders fine

The presence surface degrades *open* by design: when the dependency that
answers it is mid-incident, the route returns a successful response with an
empty payload instead of an error, and the dashboard renders an empty rail
that fills in on the next poll. Three availability classes trigger it:

* **Transient Postgres unavailability** — a brief database blip or rolling
  restart.
* **PgBouncer query timeouts** — a connection-pool timeout on the
  authentication read that runs before the handler.
* **Tenant-schema provisioning gaps** — a just-created or just-restored
  organization whose workspace schema is still finishing setup.

The degrade applies to the three always-on presence calls:

* `GET /api/v1/team-chat/presence` — the who's-online snapshot; degrades to
  an empty online list.
* `DELETE /api/v1/team-chat/presence` — the graceful go-offline on sign-out;
  degrades to a no-op acknowledgement.
* `POST /api/v1/team-chat/presence/heartbeat` — the beat each signed-in seat
  sends roughly every 25 seconds; degrades to a no-op acknowledgement.

Message threads, channels, and member lists are not on this path. They are
database reads with their own retry behaviour, so during a degrade only the
presence layer empties while the rest of Team Chat renders normally.

**What to do:** wait one poll cycle and refresh the rail. If it refills
within a minute, a transient availability fault degraded as designed and
there is nothing to fix. If the rail stays empty across refreshes and
across seats for more than a few minutes, treat it as a platform incident
and escalate with the bundle below.

## Messages appear for me but not for one teammate

The member roster, the who's-online snapshot, and the message list are
independent reads served by different backends, and they do not fail
together. A stale presence snapshot can render fine while a newer message
fetch fails for one seat, and the reverse.

The asymmetry to check first is the degrade above: the snapshot is the
surface that degrades to empty, so during a degrade one seat sees nobody
online while a seat whose snapshot succeeded a moment earlier still shows
the old online state. Neither view is wrong at that instant; they are two
snapshots of a moving picture taken a moment apart.

Presence also expires by design. A seat whose tab closed without a
graceful sign-out stops beating and ages out of the online window about a
minute after its last heartbeat, while that person's messages and
membership keep reading fine. A teammate who appears offline but answers
normally is usually this case, not a fault.

**What to do:** have the affected teammate refresh Team Chat once. If the
asymmetry survives a refresh on both seats and persists for more than a
few minutes, record what each seat sees and escalate.

## Reactions remove silently, or a reaction reappears

Reaction state (who reacted with which emoji, and the counts) lives in the
realtime cache (Redis) layer, not in the message database, and that layer
has a degrade contract:

* Reads and adds during a cache outage degrade to an empty summary or a
  best-effort write.
* A **remove** during a cache outage acknowledges `204` as a no-op instead
  of claiming `404`. The platform genuinely cannot tell at that moment
  whether you had reacted, so it refuses to claim "you had not reacted."

The visible consequence: you remove a reaction during a cache outage, see
the operation succeed, and once the cache recovers the reaction is still
there. It was never a false 404 and it is not a permission bug; the remove
simply did not land. Remove it again once the incident is over.

The surfaces sensitive to this contract are: the who's-online list and
last-seen, typing indicators, reaction adds/removes/summaries, and the
huddle live badge and participant list. Message text, channel and DM
rosters, and membership are database-backed and unaffected by it.

## Huddle shows a live badge with no one in it

A huddle's live badge is backed by two things that converge over time: the
media room's participant list and a live-huddle marker. When the last
participant leaves, the media room reclaims itself a couple of minutes
later, and the marker clears either on the leave path or on the next
status read: any status poll that finds a marker with no live participants
and no start in flight treats the huddle as ended and clears the badge for
everyone. When that read-path self-heal has not had a chance to run yet,
for example because nobody has polled the channel's huddle status since it
emptied, the badge can briefly linger as live with zero participants, and
a join in that window lands in an empty room.

Both layers are bounded. The empty room is reclaimed a couple of minutes
after the last participant leaves, and even a marker the self-heal never
reaches expires on its own within twelve hours, so a phantom badge is
always temporary, never permanent.

**What to do:** open the huddle once and leave it. Your join gives the
room a real participant, and your leave, as the last one out, runs the
cleanup that clears the badge for everyone. A channel refresh alone also
runs the read-path self-heal and clears a marker with no live
participants. If a channel still shows a live huddle with zero
participants an hour after the last real participant left, escalate with
the channel id.

## Joining a huddle returns 409 CONFLICT

Huddle end is recorded asynchronously: the media room closes first and the
live-huddle record settles a beat later, in a short flush window. A join
that lands inside that window hits a huddle that is on its way out and
returns `409 CONFLICT` instead of half-joining a dying room.

**What to do:** wait a few seconds and join again. The retry succeeds on
whichever side the race settled: it either joins the huddle that turned
out to stay live, or starts a fresh one. One retry after a pause is safe;
do not loop retries back-to-back.

## Team Chat attachment upload fails

Team Chat file sharing uses the same storage plane as Inbox ticket
attachments, so a `503 ATTACHMENT_STORAGE_UNAVAILABLE` on a Team Chat
upload is the same fault as on a ticket: a storage-backend fault that fired
after every validation gate passed, never a problem with your file.

**What to do:** follow
[Ticket attachment storage unavailable](/troubleshooting/attachment-storage-unavailable):
re-upload the same file with backoff, and if the same file keeps failing,
escalate with the response's `meta.request_id` and the file's checksum.

## Escalate

When a state does not clear on refresh and outlives the windows above,
open a support ticket with:

* a **HAR trace** of the Team Chat view reproducing the symptom, captured
  on the affected seat's browser;
* the **route URL** of the surface that showed it (the channel or DM you
  had open);
* the **message id, huddle channel id, or message id plus emoji** the
  symptom is about, where one is visible;
* the affected seat and organization, and the time window with timezone.

Where the symptom produced an error response rather than an empty state,
include the `meta.request_id` from each failed response; it pins the exact
attempt in the platform log.

## What NOT to do

* **Do not advise log-out / log-in.** Presence is keyed off the workspace
  session's heartbeats and is independent of SSO session state; signing out
  and back in rebuilds nothing on the server side. Recovery comes from the
  next poll, not from a fresh session.
* **Do not advise clearing localStorage or browser storage.** Team Chat
  drafts are stored server-side, and no client-side cache feeds the
  who's-online rail or the huddle badge, so clearing storage fixes none of
  the states on this page.
* **Do not delete and recreate the channel or DM.** The states above are
  presence- and cache-layer state; the messages, roster, and membership
  underneath them are intact.
* **Do not disable huddles or reactions for the organization.** A degrade
  is a transient backend fault handling itself by design; switching a
  feature off hides a retryable gap behind a configuration change.

## See also

* [Team Chat guide](/guides/team-chat) — the operator guide for channels,
  direct messages, and huddles.
* [Team Chat concept](/concepts/team-chat-model) — the data model behind
  channels, DMs, and huddles.
* [Ticket attachment storage unavailable](/troubleshooting/attachment-storage-unavailable) —
  the shared attachment storage runbook referenced above.
* [Error Code Reference](/reference/error-codes) — the envelope shape that
  carries the `request_id` you escalate with.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.