{"record":{"id":"ced27ae7f3de660b","repo":"gastownhall/beads","slug":"interrupted-waiting-for-cache-lock-on-s-w","errorCode":null,"errorMessage":"interrupted waiting for cache lock on %s: %w","messagePattern":"interrupted waiting for cache lock on (.+?): %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/remotecache/cache.go","lineNumber":262,"sourceCode":"\t// Poll with timeout\n\tdeadline := time.Now().Add(2 * time.Minute)\n\tfor {\n\t\terr := lockfile.FlockExclusiveNonBlocking(f)\n\t\tif err == nil {\n\t\t\treturn f, nil\n\t\t}\n\t\tif !lockfile.IsLocked(err) {\n\t\t\t_ = f.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\tif time.Now().After(deadline) {\n\t\t\t_ = f.Close()\n\t\t\treturn nil, fmt.Errorf(\"timeout waiting for cache lock on %s\", remoteURL)\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\t_ = f.Close()\n\t\t\treturn nil, fmt.Errorf(\"interrupted waiting for cache lock on %s: %w\", remoteURL, ctx.Err())\n\t\tcase <-time.After(100 * time.Millisecond):\n\t\t}\n\t}\n}\n\n// releaseLock releases a cache entry file lock.\n// The lock file is intentionally NOT removed: deleting it after unlock creates\n// a TOCTOU race where another process's newly-acquired lock gets deleted.\n// Stale lock files are cleaned up by acquireLock's age check instead.\nfunc (c *Cache) releaseLock(f *os.File) {\n\tif f != nil {\n\t\t_ = lockfile.FlockUnlock(f)\n\t\t_ = f.Close()\n\t}\n}\n\n// readMeta reads the cache metadata for a remote URL.\nfunc (c *Cache) readMeta(remoteURL string) *CacheMeta {","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/remotecache/cache.go#L244-L280","documentation":"acquireLock() selects on both the lock poll and the caller's context while waiting for the cache lock. This error means the context passed to Ensure()/Push() was cancelled or its deadline expired while blocked on lock contention. The underlying ctx.Err() (context.Canceled or context.DeadlineExceeded) is wrapped so callers can errors.Is() it.","triggerScenarios":"Cache.Ensure() or Cache.Push() called with a context that gets cancelled (Ctrl-C/SIGINT, timeout, parent cancellation) while another process holds the .lock for the same remoteURL.","commonSituations":"User hits Ctrl-C during a slow sync blocked behind another process; command run with a short timeout (e.g. exec.CommandContext or HTTP request context) that expires while waiting; orchestrator cancelling jobs.","solutions":["Treat as expected cancellation: check errors.Is(err, context.Canceled) / context.DeadlineExceeded and unwind cleanly.","If wait time is legitimate, pass a longer-lived context (context.Background() or a bigger deadline).","Resolve the underlying contention (see lock timeout guidance) so waits are short.","Retry the operation with a fresh context once the other process finishes."],"exampleFix":"// before: cancelling a background sync after 5s\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\ndefer cancel()\n_, err := cache.Ensure(ctx, url) // lock wait often exceeds 5s\n// after: generous deadline for lock waits\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)\ndefer cancel()\n_, err := cache.Ensure(ctx, url)","handlingStrategy":"try-catch","validationCode":"// check the context budget you're giving the call\ndeadline, ok := ctx.Deadline()\nif ok && time.Until(deadline) < 30*time.Second {\n    fmt.Fprintln(os.Stderr, \"warning: context may expire while waiting for cache lock\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := cache.Ensure(ctx, url); err != nil {\n    switch {\n    case errors.Is(err, context.Canceled):\n        return fmt.Errorf(\"sync cancelled by user: %w\", err)\n    case errors.Is(err, context.DeadlineExceeded):\n        return fmt.Errorf(\"sync deadline too short for lock wait: %w\", err)\n    }\n    return err\n}","preventionTips":["Pass a context with a deadline that accommodates up to ~2 minutes of lock waiting.","Wire SIGINT/SIGTERM to context cancellation and treat this error as normal shutdown.","Don't reuse short-lived HTTP/request contexts for long cache operations.","Use errors.Is against context.Canceled/DeadlineExceeded to distinguish cancellation from real lock failures."],"tags":["go","context","cancellation","locking"],"backgroundTag":"context-cancelled","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}