{"record":{"id":"7e9c204269b04500","repo":"larksuite/cli","slug":"app-id-must-not-be-empty","errorCode":null,"errorMessage":"app ID must not be empty","messagePattern":"app ID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/lockfile/lockfile.go","lineNumber":35,"sourceCode":"// safeIDChars strips path-traversal chars from app IDs.\nvar safeIDChars = regexp.MustCompile(`[^a-zA-Z0-9._-]`)\n\n// ErrHeld signals retryable contention; callers errors.Is to distinguish from real failures.\nvar 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 {","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/lockfile/lockfile.go#L17-L53","documentation":"ForSubscribe builds a per-app subscribe lock filename from the app ID and rejects an empty app ID up front, because an empty ID would produce an ambiguous shared lock file (\"subscribe_.lock\") and indicates the caller never configured credentials. It returns a plain descriptive error (no sentinel).","triggerScenarios":"Calling lockfile.ForSubscribe(\"\") — e.g. event subscribe executed before any app credentials were configured, or a config/flag that yields an empty app_id string.","commonSituations":"User ran `lark event subscribe` without ever running auth/config setup; env var or config file has an empty app_id value; a wrapper script passed an unset variable.","solutions":["Run the CLI auth/config setup so app_id is populated in the profile config.","Pass the app ID explicitly via the command's flag/argument.","Check the environment variable feeding the app ID is not unset/empty in your shell or CI.","Validate app ID non-empty in wrapper scripts before invoking the CLI."],"exampleFix":"// before\nlock, err := lockfile.ForSubscribe(appID) // appID == \"\"\n// after\nif appID == \"\" {\n    return fmt.Errorf(\"no app configured: run `lark auth` or pass --app-id\")\n}\nlock, err := lockfile.ForSubscribe(appID)","handlingStrategy":"validation","validationCode":"if appID == \"\" {\n    return errors.New(\"no app configured: run `lark auth` or pass --app-id\")\n}\nlock, err := lockfile.ForSubscribe(appID)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run CLI auth/config setup before event subscribe","Fail fast on empty app_id in wrapper scripts and CI","Surface app_id configuration in `--help`/docs so users set it first"],"tags":["config","validation","events"],"backgroundTag":"missing-config-value","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"}