{"record":{"id":"131c6fb3308023ad","repo":"gastownhall/beads","slug":"workspacegate-open-gate-s-w","errorCode":null,"errorMessage":"workspacegate: open gate %s: %w","messagePattern":"workspacegate: open gate (.+?): %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/workspacegate/gate.go","lineNumber":392,"sourceCode":"\t// Tolerate a nil context rather than panicking on ctx.Err below: gate\n\t// acquisition sits on CLI plumbing paths (cobra hooks, migrate helpers)\n\t// that tests and embedders invoke directly without the process-level\n\t// signal context, and a nil-deref here kills the whole test binary.\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\tif err := ctx.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"workspacegate: acquiring %s: %w\", g.path, err)\n\t}\n\tpoll := opts.PollInterval\n\tif poll <= 0 {\n\t\tpoll = 100 * time.Millisecond\n\t}\n\tdeadline := time.Now().Add(opts.Wait)\n\n\tf, err := os.OpenFile(g.path, os.O_CREATE|os.O_RDWR, 0o600)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"workspacegate: open gate %s: %w\", g.path, err)\n\t}\n\n\ttry := lockfile.FlockSharedNonBlock\n\tif mode == Exclusive {\n\t\ttry = lockfile.FlockExclusiveNonBlock\n\t}\n\n\tnotified := false\n\tfor {\n\t\terr := try(f)\n\t\tif err == nil {\n\t\t\th := &Handle{gate: g, mode: mode, f: f}\n\t\t\tif mode == Exclusive {\n\t\t\t\tg.writeInfo(opts.Reason)\n\t\t\t}\n\t\t\treturn h, nil\n\t\t}\n\t\tif !errors.Is(err, lockfile.ErrLockBusy) && !lockfile.IsLocked(err) {","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workspacegate/gate.go#L374-L410","documentation":"Acquire in internal/workspacegate could not open (or create) the gate lock file at g.path with O_CREATE|O_RDWR and 0o600 permissions. The underlying os.PathError is wrapped with %w so the caller can inspect it via errors.Is/As. This is a filesystem-level failure before any advisory locking is attempted, so no lock state was changed.","triggerScenarios":"Calling Acquire (via mustAcquire) where os.OpenFile on the gate path fails: the parent directory does not exist, the path is a directory, or the process lacks write permission on an existing gate file.","commonSituations":"Gate path pointing into a nonexistent or read-only directory; running under a different uid than the gate file owner (0o600 means only the owner can open it read-write); gate path accidentally configured as a directory; sandboxed CI containers with restricted mounts.","solutions":["Check the gate path: ensure its parent directory exists (os.MkdirAll) and the path itself is not a directory.","Verify the current user can create/write the file: run ls -ld on the path and parent, or touch the file manually.","If the gate file exists but is owned by another user, remove it or chown/chmod it so the process can open it O_RDWR.","If running in a container/sandbox, mount or configure a writable directory for the gate path."],"exampleFix":"// before\ng := workspacegate.New(\"/nonexistent/dir/gate.lock\")\nh, err := g.Acquire(ctx, workspacegate.Exclusive, opts)\n\n// after\nos.MkdirAll(\"/var/run/myapp\", 0o755)\ng := workspacegate.New(\"/var/run/myapp/gate.lock\")\nh, err := g.Acquire(ctx, workspacegate.Exclusive, opts)","handlingStrategy":"try-catch","validationCode":"if fi, err := os.Stat(dirOfGatePath); err != nil || !fi.IsDir() {\n    return fmt.Errorf(\"gate directory unavailable: %s\", dirOfGatePath)\n}\nif fi, err := os.Stat(gatePath); err == nil && fi.IsDir() {\n    return fmt.Errorf(\"gate path is a directory: %s\", gatePath)\n}","typeGuard":null,"tryCatchPattern":"h, err := g.Acquire(ctx, mode, opts)\nvar pe *os.PathError\nif err != nil {\n    if errors.As(err, &pe) && errors.Is(pe.Err, os.ErrPermission) {\n        return fmt.Errorf(\"cannot access gate %s as current user\", pe.Path)\n    }\n    return err\n}","preventionTips":["Create the gate directory with os.MkdirAll at startup before any Acquire.","Run all processes that share a gate as the same user, or widen the 0o600 mode deliberately.","Keep gate files on writable local storage."],"tags":["filesystem","file-permissions","locking","workspacegate"],"backgroundTag":"file-open-permission-denied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}