Concurrency Safety Issue in create_issue
Severity: CRITICAL
Tool: create_issue
File: actions/setup/js/create_issue.cjs
Analysis Date: 2026-05-20
Summary
The create_issue tool contains three shared mutable Map objects that are accessed and modified across multiple concurrent tool invocations without any synchronization. This creates critical race conditions that can lead to:
- Lost parent issue assignments (multiple parent issues created for same group)
- Lost temporary ID mappings (broken cross-references between issues)
- Incorrect deduplication state (duplicate issues created despite dedup being enabled)
Issue Details
Type: Shared Mutable Data Structures
Locations:
create_issue.cjs:635 — parentIssueCache = new Map()
create_issue.cjs:632 — temporaryIdMap = new Map()
create_issue.cjs:614 — createdTitlesByRepo = new Map()
create_issue.cjs:616 — repoTitleDedupCandidatesCache = new Map()
Code Pattern:
// ❌ UNSAFE: Shared mutable Maps accessed by all concurrent invocations
function main(config, context, github, core) {
// ...
const temporaryIdMap = new Map(); // Line 632
const createdTitlesByRepo = new Map(); // Line 614
const repoTitleDedupCandidatesCache = new Map(); // Line 616
const parentIssueCache = new Map(); // Line 635
return async function handleCreateIssue(message, resolvedTemporaryIds) {
// Multiple concurrent calls share these Maps!
// Race 1: Parent issue cache
let groupParentNumber = parentIssueCache.get(groupId); // Line 1123
if (!groupParentNumber) {
groupParentNumber = await findOrCreateParentIssue({...}); // Line 1129
parentIssueCache.set(groupId, groupParentNumber); // Line 1143 - RACE!
}
// Race 2: Temporary ID map
temporaryIdMap.set(normalizedTempId, { repo, number }); // Line 1065 - RACE!
// Race 3: Title dedup tracking
const titles = createdTitlesByRepo.get(repo) || []; // Line 626 - RACE!
titles.push({ title, normalizedTitle }); // Line 627 - RACE!
createdTitlesByRepo.set(repo, titles); // Line 628 - RACE!
};
}
Race Condition Scenarios: See detailed timeline analysis in full issue body.
Root Cause
JavaScript Concurrency Model: Node.js single-threaded event loop creates interleaving through async/await. When a function awaits, control returns to event loop, allowing other async functions to execute and access shared mutable state.
Recommended Fix
Approach: State Isolation — Move Maps into batch-scoped context
// ✅ SAFE: Properly isolated state
function main(config, context, github, core) {
return function createBatchHandler() {
// Fresh state for each batch
const temporaryIdMap = new Map();
const parentIssueCache = new Map();
// ...
return async function handleCreateIssue(message) {
// Isolated state per batch!
};
};
}
Testing Strategy
Verify fix with concurrent execution tests:
test('concurrent calls without race conditions', async () => {
const batchHandler = handler();
const promises = Array(10).fill(0).map(() => batchHandler(msg));
const results = await Promise.all(promises);
// Verify exactly one parent per group
expect(uniqueParents.size).toBe(1);
});
Priority: P0-Critical
Effort: Medium (2-3 hours)
Expected Impact: Prevents data races causing lost updates and broken cross-references
Generated by 📊 Daily MCP Tool Concurrency Analysis · ● 7.3M · ◷
Concurrency Safety Issue in
create_issueSeverity: CRITICAL
Tool:
create_issueFile:
actions/setup/js/create_issue.cjsAnalysis Date: 2026-05-20
Summary
The
create_issuetool contains three shared mutable Map objects that are accessed and modified across multiple concurrent tool invocations without any synchronization. This creates critical race conditions that can lead to:Issue Details
Type: Shared Mutable Data Structures
Locations:
create_issue.cjs:635—parentIssueCache = new Map()create_issue.cjs:632—temporaryIdMap = new Map()create_issue.cjs:614—createdTitlesByRepo = new Map()create_issue.cjs:616—repoTitleDedupCandidatesCache = new Map()Code Pattern:
Race Condition Scenarios: See detailed timeline analysis in full issue body.
Root Cause
JavaScript Concurrency Model: Node.js single-threaded event loop creates interleaving through
async/await. When a functionawaits, control returns to event loop, allowing other async functions to execute and access shared mutable state.Recommended Fix
Approach: State Isolation — Move Maps into batch-scoped context
Testing Strategy
Verify fix with concurrent execution tests:
Priority: P0-Critical
Effort: Medium (2-3 hours)
Expected Impact: Prevents data races causing lost updates and broken cross-references