Install
$ agentstack add skill-techymt-claude-code-superpowers-async-concurrency ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ● Shell / process execution Used
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Async Concurrency
The pattern
Claude Code's tool execution is mostly sequential — one tool at a time — but read-only tools can run in parallel when isConcurrencySafe() returns true. Cancellation is propagated via AbortController passed through ToolUseContext. Parallel initialization uses Promise.all(). Streaming partial results uses onProgress callbacks fed into a StreamingToolExecutor.
The key design: concurrency safety is a per-call decision, not a per-tool constant. A bash tool reading git status is safe to run concurrently. The same bash tool running npm install is not. The tool knows its own input and can make the right call.
Why this matters
In a developer session, the LLM often requests several read operations in sequence (read file A, read file B, check git status). If these were strictly sequential, the user would wait for three round-trips to disk. isConcurrencySafe: true tells the scheduler it can run these in parallel, reducing perceived latency.
Cancellation is critical for long-running tools (bash commands, web fetches). When the user presses Ctrl+C, the AbortController's signal fires and all in-flight tool calls must stop promptly. A tool that ignores context.abortController.signal will keep running after the user cancelled, blocking the next turn.
Git status is a concrete example: Claude Code parallelizes five git commands — branch, default branch, status, recent log, and user name — with a single Promise.all(). This collapses what would be ~500ms of sequential I/O into a single ~100ms concurrent batch.
How to apply it
- For
isConcurrencySafe(input): returntrueonly for operations that don't mutate state. File reads, globs, searches, and git status commands are safe. File writes, bash mutations, and agent spawns are not. - For
isReadOnly(input): returntruefor operations that make no persistent changes. Used to determine if the tool can be skipped in speculation mode. - Pass
context.abortController.signalto all async I/O:fetch(),execFile(), file streams. This ensures immediate cancellation on user interrupt. - Check
signal.abortedat checkpoints in long-running loops. - When a tool runs multiple I/O operations that are independent, use
Promise.all()— don't await them sequentially. - For streaming partial results, call
onProgress(progressData)as work proceeds. The UI subscribes to these calls to show live output.
In the source
// Source: src/context.ts (parallel git status — five concurrent commands)
export const getGitStatus = memoize(async (): Promise => {
const isGit = await getIsGit()
if (!isGit) return null
// Five independent git commands run in parallel with Promise.all
// Sequential would take ~500ms; parallel takes ~100ms
const [branch, mainBranch, status, log, userName] = await Promise.all([
getBranch(),
getDefaultBranch(),
execFileNoThrow(gitExe(), ['--no-optional-locks', 'status', '--short']),
execFileNoThrow(gitExe(), ['--no-optional-locks', 'log', '--oneline', '-n', '5']),
execFileNoThrow(gitExe(), ['config', 'user.name']),
])
// ... format and return
})
// Source: src/Tool.ts (isConcurrencySafe is input-dependent for BashTool)
export const BashTool: Tool = {
isConcurrencySafe(input) {
// Only safe if the command is classified as search/read
const classification = isSearchOrReadBashCommand(input.command)
return classification.isSearch || classification.isRead
},
isReadOnly(input) {
const classification = isSearchOrReadBashCommand(input.command)
return classification.isSearch || classification.isRead
},
async call(args, context, canUseTool) {
// AbortController signal passed to shell execution
const result = await executeShell(args.command, {
signal: context.abortController.signal,
cwd: getCwd(),
})
return { type: 'success', data: result.stdout }
},
}
// Source: src/Tool.ts (onProgress for streaming output)
async call(args, context, canUseTool, parentMessage, onProgress) {
const proc = spawnProcess(args.command)
proc.stdout.on('data', (chunk: Buffer) => {
// Stream partial output to UI as it arrives
onProgress?.({
type: 'output',
content: chunk.toString(),
})
})
const exitCode = await proc.exitCode
return { type: 'success', data: proc.collectedOutput }
}
The memoize() on getGitStatus is subtle: git status is called at conversation start and might be called again if a tool triggers re-evaluation. Memoizing prevents running all five git commands twice. The cache is cleared when state changes (e.g., branch switch).
Apply it to your code
Before — read-only tool that blocks concurrent execution unnecessarily:
export const SearchTool: Tool = {
// Wrong: returns false for a read-only operation, forcing serialization
isConcurrencySafe: (_input) => false,
isReadOnly: (_input) => false,
async call(args, context, canUseTool) {
// No signal passed — ignores cancellation
const results = await searchFiles(args.pattern, args.directory)
return { data: results }
},
}
After — tool correctly declares concurrency and respects cancellation:
import { AbortError } from '../../utils/errors.js'
export const SearchTool: Tool = {
// Search is always read-only — safe to run in parallel with other searches
isConcurrencySafe: (_input) => true,
isReadOnly: (_input) => true,
async call(args, context, canUseTool, _parentMessage, onProgress) {
const results: string[] = []
for await (const match of streamSearchResults(args.pattern, args.directory)) {
// Check cancellation at each result — don't process after user cancelled
if (context.abortController.signal.aborted) {
throw new AbortError('Search was cancelled.')
}
results.push(match)
// Stream partial results to UI for responsive feedback
onProgress?.({ type: 'partial_results', count: results.length })
}
return { data: results }
},
}
Signals that you need this pattern
- A read-only tool (file read, glob, grep) has
isConcurrencySafe: () => false - Long-running tools don't pass
context.abortController.signalto their I/O operations - A tool makes 3+ independent async calls sequentially when they could be
Promise.all()-ed - User reports that Ctrl+C doesn't stop a tool and the session hangs
- The UI shows no progress during a long operation even though partial results are available
Signals that you're over-applying it
- Mutations (writes, spawns, deletes) must never return
isConcurrencySafe: true— there are no legitimate parallel-mutation patterns here - Don't use
Promise.all()for operations that depend on each other's output onProgressis optional — don't emit progress for instant operations; the overhead is not worth it
Works with
tool-definition— whereisConcurrencySafeandisReadOnlyare declarederror-handling— handling AbortError from cancelled async operationshot-paths— parallel initialization as a startup performance pattern
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: TechyMT
- Source: TechyMT/claude-code-superpowers
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.