AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Async Concurrency

skill-techymt-claude-code-superpowers-async-concurrency · by TechyMT

|

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-techymt-claude-code-superpowers-async-concurrency

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-techymt-claude-code-superpowers-async-concurrency)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Async Concurrency? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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

  1. For isConcurrencySafe(input): return true only 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.
  2. For isReadOnly(input): return true for operations that make no persistent changes. Used to determine if the tool can be skipped in speculation mode.
  3. Pass context.abortController.signal to all async I/O: fetch(), execFile(), file streams. This ensures immediate cancellation on user interrupt.
  4. Check signal.aborted at checkpoints in long-running loops.
  5. When a tool runs multiple I/O operations that are independent, use Promise.all() — don't await them sequentially.
  6. 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.signal to 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
  • onProgress is optional — don't emit progress for instant operations; the overhead is not worth it

Works with

  • tool-definition — where isConcurrencySafe and isReadOnly are declared
  • error-handling — handling AbortError from cancelled async operations
  • hot-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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.