m1k1o/neko · warning

ErrIsAlreadyHosted

ErrIsAlreadyHosted

Error message

is already hosted

What it means

The declared sentinel ErrIsAlreadyHosted in server/internal/websocket/handler/control.go is returned by controlRequest when another session currently owns host control and the requester cannot take it (host is locked to that session). Neko keeps a single host at a time; control takeover requires the current host to release or be displaced under the configured policy.

Source

Thrown at server/internal/websocket/handler/control.go:16

package handler

import (
	"errors"

	"github.com/m1k1o/neko/server/pkg/types"
	"github.com/m1k1o/neko/server/pkg/types/event"
	"github.com/m1k1o/neko/server/pkg/types/message"
	"github.com/m1k1o/neko/server/pkg/xorg"
)

var (
	ErrIsNotAllowedToHost = errors.New("is not allowed to host")
	ErrIsNotTheHost       = errors.New("is not the host")
	ErrIsAlreadyTheHost   = errors.New("is already the host")
	ErrIsAlreadyHosted    = errors.New("is already hosted")
)

func (h *MessageHandlerCtx) controlRelease(session types.Session) error {
	if !session.Profile().CanHost || session.PrivateModeEnabled() {
		return ErrIsNotAllowedToHost
	}

	if !session.IsHost() {
		return ErrIsNotTheHost
	}

	h.desktop.ResetKeys()
	session.ClearHost()

	return nil
}

func (h *MessageHandlerCtx) controlRequest(session types.Session) error {

View on GitHub (pinned to b0f01cedea)

Solutions

  1. Wait for the current host to release, or ask them to release control.
  2. Have an admin use the admin host-transfer/kick controls to free the lock.
  3. Destroy the stale host session if it is dead (admin session list).
  4. Surface the current host's identity in the client UI so users know control is taken.

Example fix

// before
socket.on(event.CONTROL_REQUEST + 'error', () => retryTakeControl())
// after
socket.on(event.CONTROL_REQUEST + 'error', (e) => {
  if (e.message === 'is already hosted') showHostTakenBanner()
})
Defensive patterns

Strategy: try-catch

Validate before calling

// client-side, before requesting control
if (state.hostId != null && state.hostId !== state.sessionId) {
  showHostTakenBanner(state.hostId)
  return
}

Type guard

function isControlFree(state) {
  return state.hostId == null
}

Try / catch

try {
  socket.emit('control/request')
} catch (e) {
  if (e.message === 'is already hosted') {
    showHostTakenBanner() // wait or ask current host to release
  }
}

Prevention

When it happens

Trigger: Sending a control request while host != null and host != own session — i.e. someone else already controls the remote desktop and has not released it.

Common situations: Multiple participants clicking 'take control' concurrently; a host who walked away without releasing; integrations where the host lock was left held by a zombie session.

Related errors


AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01). Data as JSON: /api/errors/3e7283e62c1c8358. Report an issue: GitHub.