{"record":{"id":"54e86058fd4f900a","repo":"github/copilot-sdk","slug":"setup-must-only-be-called-once","errorCode":null,"errorMessage":"Setup must only be called once","messagePattern":"Setup must only be called once","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/internal/embeddedcli/embeddedcli.go","lineNumber":85,"sourceCode":"\t\tpanic(\"Cli reader is required\")\n\t}\n\tif len(cfg.CliHash) != sha256.Size {\n\t\tpanic(fmt.Sprintf(\"CliHash must be a SHA-256 hash (%d bytes), got %d bytes\", sha256.Size, len(cfg.CliHash)))\n\t}\n\tif cfg.LinuxMuslCli != nil && len(cfg.LinuxMuslCliHash) != sha256.Size {\n\t\tpanic(fmt.Sprintf(\"LinuxMuslCliHash must be a SHA-256 hash (%d bytes), got %d bytes\", sha256.Size, len(cfg.LinuxMuslCliHash)))\n\t}\n\tif cfg.LinuxMuslRuntimeLib != nil && len(cfg.LinuxMuslRuntimeLibHash) != sha256.Size {\n\t\tpanic(fmt.Sprintf(\"LinuxMuslRuntimeLibHash must be a SHA-256 hash (%d bytes), got %d bytes\", sha256.Size, len(cfg.LinuxMuslRuntimeLibHash)))\n\t}\n\tvalidateRuntimePairConfig(cfg.RuntimeExecutable, cfg.RuntimeExecutableHash, cfg.RuntimeNode, cfg.RuntimeNodeHash, \"\")\n\tvalidateRuntimePairConfig(cfg.LinuxMuslRuntimeExecutable, cfg.LinuxMuslRuntimeExecutableHash, cfg.LinuxMuslRuntimeNode, cfg.LinuxMuslRuntimeNodeHash, \"LinuxMusl\")\n\tvalidateOptionalHash(cfg.RuntimeAssets, cfg.RuntimeAssetsHash, \"RuntimeAssetsHash\")\n\tvalidateOptionalHash(cfg.LinuxMuslRuntimeAssets, cfg.LinuxMuslRuntimeAssetsHash, \"LinuxMuslRuntimeAssetsHash\")\n\tsetupMu.Lock()\n\tdefer setupMu.Unlock()\n\tif setupDone {\n\t\tpanic(\"Setup must only be called once\")\n\t}\n\tif pathInitialized {\n\t\tpanic(\"Setup must be called before Path is accessed\")\n\t}\n\tconfig = cfg\n\tsetupDone = true\n}\n\nvar Path = sync.OnceValue(func() string {\n\tsetupMu.Lock()\n\tdefer setupMu.Unlock()\n\tif !setupDone {\n\t\treturn \"\"\n\t}\n\tpathInitialized = true\n\tpath := install()\n\treturn path\n})","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/go/internal/embeddedcli/embeddedcli.go#L67-L103","documentation":"Setup() is the one-time initializer for the embedded CLI package: it validates the runtime configuration, stores it in a package-level variable, and flips the setupDone flag. The library deliberately panics if Setup is called a second time because re-initialization would silently replace an already-locked configuration used by Path() and install helpers. This is an intentional fail-fast invariant, not an unexpected condition.","triggerScenarios":"Calling embeddedcli.Setup(cfg) more than once in the same process — e.g. Setup called in package init of two packages, in both a library and its test harness, or re-invoked after an earlier Setup already set setupDone=true.","commonSituations":"Multiple packages' init() functions each calling Setup; test suites where several tests each call Setup instead of using a shared sync.Once; a library wrapper that defensively re-runs Setup on every client construction.","solutions":["Guard the call with sync.Once (var once sync.Once; once.Do(func(){ embeddedcli.Setup(cfg) })) so Setup runs exactly once per process.","Move the single Setup call to main()/package init of the entrypoint and remove Setup calls from library internals.","In tests, call Setup once in TestMain or a shared helper rather than per-test."],"exampleFix":"// before\nfunc newClient() *Client {\n    embeddedcli.Setup(cfg)\n    return &Client{}\n}\n\n// after\nvar setupOnce sync.Once\nfunc initCLI() {\n    setupOnce.Do(func() { embeddedcli.Setup(cfg) })\n}\nfunc newClient() *Client {\n    initCLI()\n    return &Client{}\n}","handlingStrategy":"try-catch","validationCode":"var cliOnce sync.Once\nfunc ensureSetup() {\n    cliOnce.Do(func() { embeddedcli.Setup(cfg) })\n}","typeGuard":"func setupNotDone() bool {\n    return !embeddedcliInitialized // package-level flag you control\n}","tryCatchPattern":"func safeSetup(cfg Config) (ok bool) {\n    defer func() {\n        if r := recover(); r != nil {\n            if strings.Contains(fmt.Sprint(r), \"Setup must only be called once\") {\n                ok = true // already initialized; treat as no-op\n                return\n            }\n            panic(r)\n        }\n    }()\n    embeddedcli.Setup(cfg)\n    return true\n}","preventionTips":["Wrap Setup in sync.Once at the application entrypoint and never call it elsewhere.","Expose a single internal initCLI() helper instead of letting libraries call Setup directly.","In tests, initialize in TestMain, not per-test."],"tags":["go","panic","initialization","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}