with one click
debug-channel
Debug WebSocket channel issues
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Debug WebSocket channel issues
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | debug-channel |
| description | Debug WebSocket channel issues |
Guide for diagnosing and fixing WebSocket channel issues in Levee.
| File | Purpose |
|---|---|
lib/levee_web/channels/document_channel.ex | Channel message handlers |
lib/levee_web/channels/user_socket.ex | Socket configuration |
lib/levee/documents/session.ex | Document session GenServer |
test/levee_web/channels/document_channel_test.exs | Channel tests |
test/support/channel_case.ex | Test helpers |
1. WebSocket Connect → UserSocket.connect/3
2. Join Topic → DocumentChannel.join/3 (validates but doesn't auth)
3. Connect Document → handle_in("connect_document") (authenticates)
4. Operations → handle_in("submitOp"), handle_in("submitSignal")
5. Leave/Disconnect → terminate/2
Symptom: Client joins channel but submitOp returns errors.
Cause: Channel join succeeds without auth; connect_document must be called first.
Check:
# In document_channel.ex
def handle_in("submitOp", payload, socket) do
case socket.assigns[:authenticated] do
true -> # proceed
_ -> {:reply, {:error, %{reason: "not_authenticated"}}, socket}
end
end
Symptom: connect_document returns auth error.
Debug:
# Check token structure
JWT.verify_and_decode(tenant_id, token)
# Returns {:ok, claims} or {:error, reason}
# Common reasons:
# - :invalid_signature - wrong tenant secret
# - :token_expired - exp claim in past
# - :missing_claims - required fields missing
Symptom: Operations rejected with scope error.
Required Scopes:
| Operation | Required Scope |
|---|---|
| Join channel | None |
connect_document | doc:read |
submitOp | doc:write |
| Receive broadcasts | doc:read |
Check token scopes:
claims["scopes"] # Should include required scope
Symptom: Client doesn't receive broadcasts.
Debug Steps:
# Topic format
"document:#{tenant_id}:#{document_id}"
# In Session, verify broadcast
Phoenix.PubSub.broadcast(Levee.PubSub, topic, message)
Symptom: Operations fail with "session not found".
Cause: Session GenServer not started or crashed.
Debug:
# Check if session exists
Levee.Documents.Registry.lookup(tenant_id, document_id)
# Start session manually
Levee.Documents.Session.start_or_get(tenant_id, document_id)
defmodule LeveeWeb.DocumentChannelTest do
use LeveeWeb.ChannelCase, async: true
@tenant_id "test-tenant"
@document_id "test-doc"
setup do
TenantSecrets.register_tenant(@tenant_id, "secret")
on_exit(fn -> TenantSecrets.unregister_tenant(@tenant_id) end)
{:ok, _, socket} =
LeveeWeb.UserSocket
|> socket("user_id", %{})
|> subscribe_and_join(
LeveeWeb.DocumentChannel,
"document:#{@tenant_id}:#{@document_id}"
)
%{socket: socket}
end
end
test "connect_document with valid token", %{socket: socket} do
token = JWT.generate_test_token(@tenant_id, @document_id, "user-1")
ref = push(socket, "connect_document", %{"token" => token})
assert_reply ref, :ok, %{"clientId" => _}
end
test "connect_document with invalid token", %{socket: socket} do
ref = push(socket, "connect_document", %{"token" => "invalid"})
assert_reply ref, :error, %{"reason" => _}
end
test "submitOp after authentication", %{socket: socket} do
# First authenticate
token = JWT.generate_full_access_token(@tenant_id, @document_id, "user-1")
push(socket, "connect_document", %{"token" => token})
assert_reply _, :ok, _
# Then submit op
op = %{"type" => "op", "contents" => [...]}
ref = push(socket, "submitOp", op)
assert_reply ref, :ok, _
end
# Run channel tests with trace
mix test test/levee_web/channels/ --trace
# Run single test
mix test test/levee_web/channels/document_channel_test.exs:42
# Start server with debug logging
iex -S mix phx.server
# Then in IEx:
Logger.configure(level: :debug)
Client Channel Session
| | |
|---join(topic)----------->| |
|<--ok--------------------| |
| | |
|---connect_document------>| |
| |--validate_token------->|
| |<--{:ok, client_id}----|
|<--ok, clientId----------| |
| | |
|---submitOp-------------->| |
| |--process_op----------->|
| |<--{:ok, sequenced}----|
|<--ok--------------------| |
| | |
| |<--broadcast-----------|
|<--op broadcast----------| |