{"record":{"id":"c7e8526753c2d045","repo":"charmbracelet/crush","slug":"requested-channels-differ-from-the-existing-worksp","errorCode":null,"errorMessage":"requested channels differ from the existing workspace; channels are an explicit opt-in and are not shared across duplicate creates","messagePattern":"requested channels differ from the existing workspace; channels are an explicit opt-in and are not shared across duplicate creates","errorType":"exception","errorClass":"ErrChannelOptInMismatch","httpStatus":null,"severity":"error","filePath":"internal/backend/backend.go","lineNumber":43,"sourceCode":"\t\"github.com/charmbracelet/crush/internal/version\"\n\t\"github.com/google/uuid\"\n)\n\n// Common errors returned by backend operations.\nvar (\n\tErrWorkspaceNotFound       = errors.New(\"workspace not found\")\n\tErrLSPClientNotFound       = errors.New(\"LSP client not found\")\n\tErrAgentNotInitialized     = errors.New(\"agent coordinator not initialized\")\n\tErrPathRequired            = errors.New(\"path is required\")\n\tErrInvalidPermissionAction = errors.New(\"invalid permission action\")\n\tErrUnknownCommand          = errors.New(\"unknown command\")\n\tErrInvalidClientID         = errors.New(\"invalid client_id\")\n\tErrClientNotAttached       = errors.New(\"client not attached\")\n\tErrWorkspaceClosing        = errors.New(\"workspace closing\")\n\tErrServerShuttingDown      = errors.New(\"server is shutting down\")\n\tErrServerNotIdle           = errors.New(\"server is hosting live workspaces\")\n\tErrClientRetired           = errors.New(\"client has been retired\")\n\tErrChannelOptInMismatch    = errors.New(\"requested channels differ from the existing workspace; channels are an explicit opt-in and are not shared across duplicate creates\")\n)\n\n// DefaultCreateGrace is the window in which a client must open an SSE\n// stream after creating a workspace before its creation hold is\n// released. Exposed as a package variable so tests can shorten it.\nvar DefaultCreateGrace = 30 * time.Second\n\n// DefaultIdleShutdownDelay is how long the server stays alive after its\n// last workspace is released before it shuts itself down. The delay\n// exists so a client that closes one session and opens another moments\n// later (the same directory or a different one) reuses the still-running\n// server instead of racing its shutdown: with an immediate shutdown the\n// new client can attach to — or create a workspace on — a server that is\n// already tearing down, and then observe its coder agent as \"offline\".\n// Any workspace create within the window cancels the pending shutdown.\n// Overridable via CRUSH_SERVER_IDLE_TIMEOUT (seconds; 0 restores the\n// old shut-down-immediately behavior).\nvar DefaultIdleShutdownDelay = 60 * time.Second","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/backend/backend.go#L25-L61","documentation":"ErrChannelOptInMismatch reports that a duplicate CreateWorkspace specified a set of channels that differs from the channels the existing workspace was created with. Channels are an explicit opt-in and are deliberately not shared across duplicate creates, so an existing workspace is never silently reconfigured. Returned by CreateWorkspace.","triggerScenarios":"Calling CreateWorkspace with the same path and client when a workspace already exists at that path, but passing a different channels set than the original create (e.g. one call opts into a channel, the other does not).","commonSituations":"Two launch points (editor + CLI) creating workspaces for the same directory with differing channel configurations; config change between the first and second launch adding or removing an opted-in channel; a stale script pinned to old channel flags.","solutions":["Make the duplicate create request the same channel set the existing workspace was created with","Delete the existing workspace first, then re-create with the desired channels","Align channel configuration across all clients that share the working directory"],"exampleFix":"// before\nws, err := backend.CreateWorkspace(ctx, id, path, backend.WithChannels(\"events\")) // existing ws has none\n// after\nws, err := backend.DeleteWorkspace(ctx, id, path)\nif err != nil { return err }\nws, err = backend.CreateWorkspace(ctx, id, path, backend.WithChannels(\"events\"))","handlingStrategy":"validation","validationCode":"if existing, ok := workspaceAt(path); ok && !slices.Equal(existing.Channels, requestedChannels) {\n    // either drop the channels arg to match, or delete and re-create\n    if err := backend.DeleteWorkspace(ctx, id, path); err != nil {\n        return err\n    }\n}","typeGuard":"func isErrChannelOptInMismatch(err error) bool {\n    return errors.Is(err, backend.ErrChannelOptInMismatch)\n}","tryCatchPattern":"ws, err := backend.CreateWorkspace(ctx, id, path, opts...)\nif errors.Is(err, backend.ErrChannelOptInMismatch) {\n    // retry matching the existing workspace's channels\n    ws, err = backend.CreateWorkspace(ctx, id, path, existingChannelsOpts...)\n}\nreturn err","preventionTips":["Keep channel configuration in one shared config file for all clients of a directory","Omit the channels option on duplicate creates to inherit the existing opt-in","Delete and re-create the workspace when channel requirements genuinely change"],"tags":["configuration","workspace","channels"],"backgroundTag":"channel-opt-in-mismatch","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}