concurrency-debugging — Claude Code skill: read race reports, find
A skill that walks the agent through ThreadSanitizer reports, Helgrind lock-order graphs and stuck threads in GDB. Useful when a threaded program hangs or crashes.
npx skills add mohitmishra786/low-level-dev-skills --skill concurrency-debugging -a claude-codeWhat concurrency-debugging does
The skill lays out a workflow for concurrency bugs: how to read a TSan report, how to spot lock-order violations with Helgrind, how to inspect blocked threads in GDB. TSan builds go through clang -fsanitize=thread -g -O1 or gcc -fsanitize=thread -g -O1; Helgrind runs as valgrind --tool=helgrind --log-file=helgrind.log ./prog.
There is a table of common races with fixes: unsynchronized reads and writes on a global, double-checked locking without atomic, += on a shared counter, a container mutated while iterated. A separate section covers std::atomic misuse — why compare_exchange_strong is needed where if (counter == 0) counter = 1 leaves a TOCTOU race, and why memory_order_relaxed fails as a readiness flag. Happens-before gets its own pass: sequenced-before inside a thread, synchronizes-with across threads, thread creation and join, mutex unlock and lock.
For Rust the skill points at Arc<Mutex<T>> and compile-time guarantees. One thing to know: the reasoning and examples stick to C++ and Rust, and the tooling shown is clang, gcc, valgrind, gdb — on another toolchain you will be translating the examples yourself.
What it helps with
- read a ThreadSanitizer report and locate the conflicting accesses
- catch a lock-order violation before it turns into a deadlock
- find which threads are blocked and which mutex each one holds
- spot a TOCTOU race in a compound
std::atomicoperation - work out happens-before between threads in C++ or Rust
Example prompts for the agent
- ThreadSanitizer flagged a data race — help me read the report and fix it
- My program hangs on threads, show me how to find the deadlock in GDB
- Check whether my memory_order choices are correct here
How to install concurrency-debugging
1Skills CLI (Vercel)
npx skills add mohitmishra786/low-level-dev-skills --skill concurrency-debugging -a claude-code2Manually: put the files in place
~/.claude/skills/concurrency-debugging/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/concurrency-debugging/
1Skills CLI (Vercel)
npx skills add mohitmishra786/low-level-dev-skills --skill concurrency-debugging -a codex2Manually: put the files in place
.agents/skills/concurrency-debugging/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add mohitmishra786/low-level-dev-skills --skill concurrency-debugging -a cursor2Manually: put the files in place
.cursor/skills/concurrency-debugging/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install mohitmishra786/low-level-dev-skills concurrency-debugging2Manually: put the files in place
.github/skills/concurrency-debugging/SKILL.md1Skills CLI (Vercel)
npx skills add mohitmishra786/low-level-dev-skills --skill concurrency-debugging -a gemini-cli2Platform CLI
gemini skills install https://github.com/mohitmishra786/low-level-dev-skills --path skills/debuggers/concurrency-debuggingManually: put the files in place
.devin/skills/concurrency-debugging/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add mohitmishra786/low-level-dev-skills --skill concurrency-debugging -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about concurrency-debugging
How do I install concurrency-debugging in Claude?
Run npx skills add mohitmishra786/low-level-dev-skills --skill concurrency-debugging -a claude-code. The skill lands in ~/.claude/skills/concurrency-debugging/SKILL.md.
What does concurrency-debugging do?
It guides the agent through diagnosing races and deadlocks: reading TSan reports, checking lock order with Helgrind, inspecting blocked threads in GDB, plus std::atomic misuse patterns and happens-before reasoning.
Which languages does the skill cover?
The examples and reasoning are built around C++ and Rust. The tools it walks through are ThreadSanitizer, Helgrind and GDB.
Source text
SKILL.md · MIT
---
name: concurrency-debugging
description: Concurrency debugging skill for diagnosing data races and deadlocks. Use when reading TSan race reports, debugging deadlocks with GDB thread inspection, analyzing lock-order graphs with Helgrind, identifying std::atomic misuse patterns, or reasoning about happens-before in C++ and Rust. Activates on queries about data races, TSan reports, deadlocks, Helgrind, lock ordering, thread sanitizer output, or atomic ordering issues.
---
# Concurrency Debugging
## Purpose
Guide agents through diagnosing and fixing concurrency bugs: reading ThreadSanitizer race reports, using Helgrind for lock-order analysis, detecting deadlocks with GDB thread inspection, identifying common `std::atomic` misuse patterns, and applying happens-before reasoning in C++ and Rust.
## Triggers
- "ThreadSanitizer reported a data race — how do I read the report?"
- "My program deadlocks — how do I debug it?"
- "How do I use Helgrind to find threading bugs?"
- "Am I using std::atomic correctly?"
- "How does happens-before work in C++ memory ordering?"
- "How do I find which threads are deadlocked in GDB?"
## Workflow
### 1. ThreadSanitizer (TSan) — race detection
```bash
# Build with TSan
clang -fsanitize=thread -g -O1 -o prog main.c
# or GCC
gcc -fsanitize=thread -g -O1 -o prog main.c
# Run (TSan intercepts memory accesses at runtime)
./prog
# TSan-specific options
TSAN_OPTIONS="halt_on_error=1:second_deadlock_stack=1" ./prog
```
Reading a TSan report:
```text
WARNING: ThreadSanitizer: data race (pid=12345)
Write of size 4 at 0x7f1234 by thread T2:
#0 increment /src/counter.c:8:5 ← access site in T2
#1 worker_thread /src/counter.c:22:3
Previous read of size 4 at 0x7f1234 by thread T1:
#0 read_counter /src/counter.c:3:14 ← conflicting access in T1
#1 main /src/counter.c:30:5
Thread T2 created at:
#0 pthread_create .../tsan_interceptors.cpp
#1 main /src/counter.c:28:3
SUMMARY: ThreadSanitizer: data race /src/counter.c:8:5 in increment
```
How to read:
1. Line 1: type of access (write/read) and address
2. Stack under "Write of size": the thread that performed the write
3. Stack under "Previous read/write": the conflicting thread
4. "Thread T2 created at": where the thread was spawned
5. Fix: the `increment` and `read_counter` functions access the same address without synchronization
Common races and fixes:
| Race pattern | Fix |
|-------------|-----|
| Read/write on global without lock | Add mutex or use `std::atomic` |
| Double-checked locking without `atomic` | Use `std::once_flag` + `std::call_once` |
| `+=` on shared integer | Use `std::atomic<int>::fetch_add()` |
| Container modified while iterated | Lock entire critical section |
| `shared_ptr` ref count race | Already safe (ref count is atomic); but pointed-to object may not be |
### 2. Helgrind — lock-order and race detection
Helgrind uses Valgrind infrastructure to detect lock ordering violations (potential deadlocks) and data races:
```bash
# Run with Helgrind
valgrind --tool=helgrind --log-file=helgrind.log ./prog
# Lock order violation report
==1234== Thread #3: lock order "0x... M2" after "0x... M1"
==1234== observed (incorrect) order
==1234== at pthread_mutex_lock (helgrind/...)
==1234== by worker2 /src/worker.c:45 ← T3 takes M2 then M1
==1234==
==1234== required order established by acquisition of lock at address 0x... M1
==1234== at pthread_mutex_lock
==1234== by worker1 /src/worker.c:31 ← T1 takes M1 then M2
```
Lock-order violation = potential deadlock:
- Thread T1 acquires M1, then tries M2
- Thread T2 acquires M2, then tries M1
- Both can deadlock if they race
Fix: enforce a consistent global lock ordering. Always take M1 before M2 everywhere.
### 3. Deadlock detection with GDB
```bash
# Attach GDB to a deadlocked process
gdb -p $(pgrep prog)
# Or run under GDB then trigger deadlock
(gdb) info threads # list all threads and current state
# * 1 Thread 0x... (LWP 1234) "prog" ... in __lll_lock_wait ()
# 2 Thread 0x... (LWP 1235) "prog" ... in __lll_lock_wait ()
# Threads blocked in __lll_lock_wait = waiting for mutex
(gdb) thread 1
(gdb) bt # show which mutex thread 1 is waiting for
(gdb) thread 2
(gdb) bt # show which mutex thread 2 holds/waits
# Find the mutex owner
(gdb) p ((pthread_mutex_t*)0x601090)->__data.__owner # Linux glibc mutex
# prints TID of owning thread
# Python script to dump all mutex owners (GDB 7+)
python
import gdb
for t in gdb.selected_inferior().threads():
t.switch()
print(f"Thread {t.num}: {gdb.execute('bt 3', to_string=True)}")
end
```
### 4. std::atomic misuse patterns
```cpp
// WRONG: atomic variable, but non-atomic compound operation
std::atomic<int> counter{0};
if (counter == 0) counter = 1; // not atomic together! TOCTOU race
// CORRECT: use compare_exchange
int expected = 0;
counter.compare_exchange_strong(expected, 1);
// WRONG: relaxed ordering for sync flag
std::atomic<bool> ready{false};
// Producer:
data = 42;
ready.store(true, std::memory_order_relaxed); // WRONG: no happens-before
// CORRECT: release-acquire for publishing data
// Producer:
data = 42;
ready.store(true, std::memory_order_release); // syncs with acquire
// Consumer:
if (ready.load(std::memory_order_acquire)) { // syncs with release
use(data); // safe to read data here
}
// WRONG: using data across threads without atomic/mutex
// int shared_data; // non-atomic — UB on concurrent access
// CORRECT: protect with mutex or make atomic
std::mutex mtx;
std::unique_lock lock(mtx);
shared_data = 42;
```
### 5. Happens-before reasoning
In C++, happens-before is established by:
```
Sequenced-before (within a thread):
Statement A comes before B in code → A happens-before B
Synchronizes-with (across threads):
store(release) → load(acquire) on SAME atomic variable
→ store happens-before load
→ everything before store happens-before everything after load
Thread creation/join:
spawn(T) → any action in T (create synchronizes-with)
any action in T → join(T) (join synchronizes-before)
Mutex:
unlock(M) → lock(M) (next acquirer)
```
```cpp
// Establishing happens-before across threads
std::atomic<int> flag{0};
int data = 0;
// Thread 1:
data = 42; // A
flag.store(1, memory_order_release); // B: A sequenced-before B
// Thread 2:
while (flag.load(memory_order_acquire) != 1) {} // C: synchronizes-with B
int x = data; // D: C sequenced-before D
// D reads 42: A happens-before B synchronizes-with C sequenced-before D
// → A happens-before D
```
### 6. Rust concurrency — compile-time guarantees
Rust prevents data races at compile time via ownership:
```rust
use std::sync::{Arc, Mutex};
use std::thread;
// Shared mutable state: Arc<Mutex<T>>
let counter = Arc::new(Mutex::new(0u32));
let c = Arc::clone(&counter);
let t = thread::spawn(move || {
let mut val = c.lock().unwrap();
*val += 1;
});
t.join().unwrap();
println!("{}", *counter.lock().unwrap());
// Rust prevents:
// - Sharing &mut T across threads (Sync not impl for &mut T)
// - Moving non-Send types to threads (compiler error)
// Use TSAN_OPTIONS with cargo test if TSan checks are needed:
// RUSTFLAGS="-Z sanitizer=thread" cargo +nightly test
```
## Related skills
- Use `skills/runtimes/sanitizers` for TSan build flags and other sanitizers
- Use `skills/profilers/valgrind` for Helgrind and Memcheck integration
- Use `skills/debuggers/gdb` for advanced GDB thread inspection
- Use `skills/low-level-programming/memory-model` for C++/Rust memory ordering theory
© mohitmishra786 · MIT · mohitmishra786/low-level-dev-skills