{"record":{"id":"3e7283e62c1c8358","repo":"m1k1o/neko","slug":"errisalreadyhosted","errorCode":"ErrIsAlreadyHosted","errorMessage":"is already hosted","messagePattern":"is already hosted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/internal/websocket/handler/control.go","lineNumber":16,"sourceCode":"package handler\n\nimport (\n\t\"errors\"\n\n\t\"github.com/m1k1o/neko/server/pkg/types\"\n\t\"github.com/m1k1o/neko/server/pkg/types/event\"\n\t\"github.com/m1k1o/neko/server/pkg/types/message\"\n\t\"github.com/m1k1o/neko/server/pkg/xorg\"\n)\n\nvar (\n\tErrIsNotAllowedToHost = errors.New(\"is not allowed to host\")\n\tErrIsNotTheHost       = errors.New(\"is not the host\")\n\tErrIsAlreadyTheHost   = errors.New(\"is already the host\")\n\tErrIsAlreadyHosted    = errors.New(\"is already hosted\")\n)\n\nfunc (h *MessageHandlerCtx) controlRelease(session types.Session) error {\n\tif !session.Profile().CanHost || session.PrivateModeEnabled() {\n\t\treturn ErrIsNotAllowedToHost\n\t}\n\n\tif !session.IsHost() {\n\t\treturn ErrIsNotTheHost\n\t}\n\n\th.desktop.ResetKeys()\n\tsession.ClearHost()\n\n\treturn nil\n}\n\nfunc (h *MessageHandlerCtx) controlRequest(session types.Session) error {","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/m1k1o/neko/blob/b0f01cedea68893e85a3fd852c0521238c285695/server/internal/websocket/handler/control.go#L1-L34","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wait for the current host to release, or ask them to release control.","Have an admin use the admin host-transfer/kick controls to free the lock.","Destroy the stale host session if it is dead (admin session list).","Surface the current host's identity in the client UI so users know control is taken."],"exampleFix":"// before\nsocket.on(event.CONTROL_REQUEST + 'error', () => retryTakeControl())\n// after\nsocket.on(event.CONTROL_REQUEST + 'error', (e) => {\n  if (e.message === 'is already hosted') showHostTakenBanner()\n})","handlingStrategy":"try-catch","validationCode":"// client-side, before requesting control\nif (state.hostId != null && state.hostId !== state.sessionId) {\n  showHostTakenBanner(state.hostId)\n  return\n}","typeGuard":"function isControlFree(state) {\n  return state.hostId == null\n}","tryCatchPattern":"try {\n  socket.emit('control/request')\n} catch (e) {\n  if (e.message === 'is already hosted') {\n    showHostTakenBanner() // wait or ask current host to release\n  }\n}","preventionTips":["Display the current host's name so users know control is taken.","Don't auto-retry the request in a loop — wait for host/release events.","Use admin controls to free a lock held by a dead session.","Subscribe to control lock/release events to know when control becomes available."],"tags":["websocket","host-control","single-host-lock"],"backgroundTag":"host-already-taken","analyzedSha":"b0f01cedea68893e85a3fd852c0521238c285695","analyzedAt":"2026-09-01T10:35:56.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}