{"record":{"id":"66c8997595a4544b","repo":"larksuite/cli","slug":"create-lock-dir-w","errorCode":null,"errorMessage":"create lock dir: %w","messagePattern":"create lock dir: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/lockfile/lockfile.go","lineNumber":39,"sourceCode":"var ErrHeld = errors.New(\"lockfile: lock already held\")\n\ntype LockFile struct {\n\tpath string\n\tfile *os.File\n}\n\nfunc New(path string) *LockFile {\n\treturn &LockFile{path: path}\n}\n\n// ForSubscribe sanitises appID against path traversal before forming the lock filename.\nfunc ForSubscribe(appID string) (*LockFile, error) {\n\tif appID == \"\" {\n\t\treturn nil, fmt.Errorf(\"app ID must not be empty\")\n\t}\n\tdir := filepath.Join(core.GetConfigDir(), \"locks\")\n\tif err := vfs.MkdirAll(dir, 0700); err != nil {\n\t\treturn nil, fmt.Errorf(\"create lock dir: %w\", err)\n\t}\n\tsafe := safeIDChars.ReplaceAllString(appID, \"_\")\n\tname := filepath.Base(fmt.Sprintf(\"subscribe_%s.lock\", safe))\n\tpath := filepath.Join(dir, name)\n\treturn New(path), nil\n}\n\n// TryLock acquires an exclusive non-blocking lock; auto-released on process exit.\nfunc (l *LockFile) TryLock() error {\n\tif l.file != nil {\n\t\treturn fmt.Errorf(\"%w: %s\", ErrHeld, l.path)\n\t}\n\tf, err := vfs.OpenFile(l.path, os.O_CREATE|os.O_RDWR, 0600)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"open lock file: %w\", err)\n\t}\n\tif err := tryLockFile(f); err != nil {\n\t\tf.Close()","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/lockfile/lockfile.go#L21-L57","documentation":"ForSubscribe creates <config-dir>/locks with mode 0700 via vfs.MkdirAll before writing the lock file; if that fails it wraps the underlying error. This guards all subsequent lock-file creation against a missing or uncreatable config directory.","triggerScenarios":"vfs.MkdirAll fails while calling lockfile.ForSubscribe — e.g. the config dir is read-only, does not exist and cannot be created, disk full, or permission denied on the user's home/config path.","commonSituations":"Running in a container with a read-only or non-writable HOME; LARKSUITE_CLI_CONFIG_DIR pointing to an invalid/unwritable path; disk quota exceeded; sandboxed CI runners restricting writes.","solutions":["Check the error wrapped in %w for the concrete cause (permission vs disk vs path).","Point LARKSUITE_CLI_CONFIG_DIR to a writable directory.","Fix permissions on the config dir / home directory (mkdir/chmod as needed).","Free disk space or raise quota if the failure is ENOSPC."],"exampleFix":"// before\nexport LARKSUITE_CLI_CONFIG_DIR=/root/.config  # read-only container\n// after\nexport LARKSUITE_CLI_CONFIG_DIR=/tmp/lark-config  # writable path\n# or: chmod u+rwX ~/.config","handlingStrategy":"validation","validationCode":"cfgDir := os.Getenv(\"LARKSUITE_CLI_CONFIG_DIR\")\nif cfgDir == \"\" { cfgDir = filepath.Join(os.Getenv(\"HOME\"), \".config\") }\nif info, err := os.Stat(cfgDir); err != nil || !info.IsDir() {\n    return fmt.Errorf(\"config dir %q unavailable\", cfgDir)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Point LARKSUITE_CLI_CONFIG_DIR at a writable path in containers/CI","Check disk space and permissions on the config directory","Pre-create and chmod 0700 the config dir in setup scripts"],"tags":["filesystem","permissions","config"],"backgroundTag":"filesystem-permission-denied","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}