{"record":{"id":"088499fcc4ce7f9b","repo":"yewstack/yew","slug":"failed-to-lock-github-issue-labels-fetcher-err","errorCode":null,"errorMessage":"Failed to lock GITHUB_ISSUE_LABELS_FETCHER: {err}","messagePattern":"Failed to lock GITHUB_ISSUE_LABELS_FETCHER: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"tools/changelog/src/create_log_line.rs","lineNumber":76,"sourceCode":"            return Ok(None);\n        }\n    };\n\n    let match_to_be_stripped = captures.get(0).ok_or_else(|| {\n        anyhow!(\"Failed to capture first group - issue part of the message like \\\" (#2263)\\\"\")\n    })?;\n    let mut message = commit_first_line.clone();\n    message.replace_range(match_to_be_stripped.range(), \"\");\n\n    let issue_id = captures\n        .get(1)\n        .ok_or_else(|| anyhow!(\"Failed to capture second group - issue id like \\\"2263\\\"\"))?\n        .as_str()\n        .to_string();\n\n    let issue_labels = GITHUB_ISSUE_LABELS_FETCHER\n        .lock()\n        .map_err(|err| anyhow!(\"Failed to lock GITHUB_ISSUE_LABELS_FETCHER: {err}\"))?\n        .fetch_issue_labels(issue_id.clone(), token)\n        .with_context(|| format!(\"Could not find GitHub labels for issue: {issue_id}\"))?;\n\n    let is_issue_for_this_package = issue_labels\n        .iter()\n        .any(|label| package_labels.contains(&label.as_str()));\n\n    if !is_issue_for_this_package {\n        println!(\"Issue {issue_id} is not for {package_labels:?} packages\");\n        let leftovers = issue_labels.iter().filter(|label| {\n            !(label.starts_with(\"A-\") || *label == \"documentation\" || *label == \"meta\")\n        });\n        let count = leftovers.count();\n        if count > 0 {\n            println!(\n                \"Potentially invalidly labeled issue: {issue_id}. Neither A-* (area), documentation nor meta labels found. \\\n            inspect/re-tag at https://github.com/yewstack/yew/issues/{issue_id}\"\n            );","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/yewstack/yew/blob/0e4a05472fac4e5fce1befe60fa4a1e43a36b6a3/tools/changelog/src/create_log_line.rs#L58-L94","documentation":"The Yew changelog tool caches a `GitHubIssueLabelsFetcher` behind a global `Mutex` (create_log_line.rs:13-14). `Mutex::lock` only fails when a previous thread panicked while holding that lock, and this code converts the `PoisonError` into an anyhow error with the shown message. The lock error is therefore always a follow-on symptom: the real failure is an earlier panic inside `fetch_issue_labels` (network error, bad token, rate limit, deserialization) in the same process.","triggerScenarios":"Running `create_log_lines` over a commit range where an earlier `fetch_issue_labels` call panicked while holding `GITHUB_ISSUE_LABELS_FETCHER`; every subsequent commit processed by the same process then fails at the `.lock()` in create_log_line.rs:74-76.","commonSituations":"Changelog generation in CI with an expired or missing `GITHUB_TOKEN` (the API call panics first, then poisons the mutex), GitHub rate limiting during a large release run, or a transient network drop mid-run that turns every later commit into this lock error.","solutions":["Scroll up in the output and fix the FIRST panic — it names the actual cause (auth, rate limit, network); the lock error is only fallout","Re-run the tool in a fresh process after fixing credentials or connectivity; a new process starts with an unpoisoned mutex","As a maintainer, make `fetch_issue_labels` return `Result` instead of panicking, or recover with `.unwrap_or_else(|p| p.into_inner())` so one bad fetch cannot poison the whole run"],"exampleFix":"// before (tools/changelog/src/create_log_line.rs)\nlet issue_labels = GITHUB_ISSUE_LABELS_FETCHER\n    .lock()\n    .map_err(|err| anyhow!(\"Failed to lock GITHUB_ISSUE_LABELS_FETCHER: {err}\"))?\n    .fetch_issue_labels(issue_id.clone(), token)?;\n\n// after — recover the inner guard from a poisoned lock instead of aborting\nlet mut fetcher = GITHUB_ISSUE_LABELS_FETCHER\n    .lock()\n    .unwrap_or_else(|poisoned| poisoned.into_inner());\nlet issue_labels = fetcher\n    .fetch_issue_labels(issue_id.clone(), token)?;","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Rust has no try/catch; match on the lock result and recover the inner guard:\nlet mut fetcher = GITHUB_ISSUE_LABELS_FETCHER\n    .lock()\n    .unwrap_or_else(|poisoned| poisoned.into_inner());\n// The data the mutex guards is a stateless fetcher, so the lock itself\n// carries no invariants — using into_inner() is safe here. The original\n// panic (earlier in the log) remains the bug to fix.","preventionTips":["Treat the first panic in the log as the real bug — this lock error only appears after `fetch_issue_labels` panicked while holding the mutex","Make `fetch_issue_labels` return `Result` instead of panicking on network, token, or rate-limit failures","Run the changelog tool locally with a valid `GITHUB_TOKEN` before relying on CI to generate it"],"tags":["rust","mutex","poisoning","concurrency","changelog-tool","github-api"],"backgroundTag":"mutex-poisoned","analyzedSha":"0e4a05472fac4e5fce1befe60fa4a1e43a36b6a3","analyzedAt":"2026-08-22T21:16:31.212Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}