fix: sequence store locking, MCP auto-increment, round5 precision#17
Merged
dbejarano820 merged 2 commits intomainfrom Feb 22, 2026
Merged
fix: sequence store locking, MCP auto-increment, round5 precision#17dbejarano820 merged 2 commits intomainfrom
dbejarano820 merged 2 commits intomainfrom
Conversation
…ent, round5 precision - fix(sdk): add file-based locking to sequence store to prevent race conditions when concurrent processes call getNextSequence() simultaneously (issue #4) - fix(mcp): integrate create_invoice tool with SequenceStore for auto-incrementing sequence numbers instead of hardcoding sequence=1 on every call (issue #5) - fix(sdk): improve round5() to use Number.toFixed(5) instead of Math.round(value * factor) / factor to avoid IEEE 754 floating-point multiplication artifacts (issue #8) All 623 tests pass.
Greptile SummaryThis PR fixes three distinct issues identified during code review: sequence store race conditions, hardcoded MCP invoice sequences, and floating-point precision in tax rounding. Key Changes:
Issues Found:
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| packages/sdk/src/config/sequence-store.ts | Added mkdir-based file locking to prevent race conditions. One logic issue: stale lock removal doesn't retry acquisition. |
| packages/mcp/src/tools/create-invoice.ts | Integrated getNextSequence() to auto-increment invoice numbers. Clean implementation with proper formatting. |
| packages/sdk/src/tax/calculator.ts | Changed round5() to use toFixed() to avoid IEEE 754 multiplication artifacts. Correct fix for precision issue. |
Sequence Diagram
sequenceDiagram
participant MCP as create_invoice MCP Tool
participant Store as SequenceStore
participant Lock as File Lock (.sequences.lock)
participant File as sequences.json
MCP->>Store: getNextSequence("01", "001", "00001")
Store->>Lock: mkdir(.sequences.lock) - Atomic
alt Lock acquired
Lock-->>Store: Success
Store->>File: Read sequences.json
File-->>Store: {"01-001-00001": 42}
Store->>Store: Increment: 42 + 1 = 43
Store->>File: Write atomically via temp file
File-->>Store: Write complete
Store->>Lock: rm(.sequences.lock)
Store-->>MCP: Return 43
MCP->>MCP: Build clave with sequence=43
MCP->>MCP: Format consecutivo: "001000010000000043"
else Lock exists (EEXIST)
Lock-->>Store: EEXIST error
Store->>Store: Sleep 50ms and retry
Store->>Lock: mkdir(.sequences.lock) - Retry
else Timeout after 5s
Lock-->>Store: Still EEXIST
Store->>Lock: rm(.sequences.lock) - Force cleanup
Store-->>MCP: Throw timeout error
end
Last reviewed commit: bbf31c1
Comment on lines
+46
to
+57
| if (Date.now() >= deadline) { | ||
| // Stale lock — force remove and retry once | ||
| try { | ||
| await rm(lockPath, { recursive: true }); | ||
| } catch { | ||
| // ignore | ||
| } | ||
| throw new Error( | ||
| `Failed to acquire sequence lock at ${lockPath} after ${String(LOCK_TIMEOUT_MS)}ms. ` + | ||
| `If this persists, manually remove the lock directory.`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Race condition: stale lock removal happens AFTER deadline check fails.
When timeout occurs, the function removes the stale lock but then immediately throws an error. If another concurrent call removed the lock between the deadline check and the rm() call, this could throw unnecessarily.
Suggested change
| if (Date.now() >= deadline) { | |
| // Stale lock — force remove and retry once | |
| try { | |
| await rm(lockPath, { recursive: true }); | |
| } catch { | |
| // ignore | |
| } | |
| throw new Error( | |
| `Failed to acquire sequence lock at ${lockPath} after ${String(LOCK_TIMEOUT_MS)}ms. ` + | |
| `If this persists, manually remove the lock directory.`, | |
| ); | |
| } | |
| if (Date.now() >= deadline) { | |
| // Stale lock — force remove and retry once | |
| try { | |
| await rm(lockPath, { recursive: true }); | |
| // Retry acquisition after removing stale lock | |
| continue; | |
| } catch { | |
| // ignore | |
| } | |
| throw new Error( | |
| `Failed to acquire sequence lock at ${lockPath} after ${String(LOCK_TIMEOUT_MS)}ms. ` + | |
| `If this persists, manually remove the lock directory.`, | |
| ); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/sdk/src/config/sequence-store.ts
Line: 46-57
Comment:
Race condition: stale lock removal happens AFTER deadline check fails.
When timeout occurs, the function removes the stale lock but then immediately throws an error. If another concurrent call removed the lock between the deadline check and the rm() call, this could throw unnecessarily.
```suggestion
if (Date.now() >= deadline) {
// Stale lock — force remove and retry once
try {
await rm(lockPath, { recursive: true });
// Retry acquisition after removing stale lock
continue;
} catch {
// ignore
}
throw new Error(
`Failed to acquire sequence lock at ${lockPath} after ${String(LOCK_TIMEOUT_MS)}ms. ` +
`If this persists, manually remove the lock directory.`,
);
}
```
How can I resolve this? If you propose a fix, please make it concise.Address Greptile review: after removing a stale lock, continue the loop to retry mkdir instead of immediately throwing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes from Code Review
Addresses 3 code issues found during the full codebase review.
🔒 #4 — Sequence store race condition
File:
packages/sdk/src/config/sequence-store.tsAdded file-based locking via atomic
mkdirto prevent concurrentgetNextSequence()calls from reading the same value and producing duplicate sequence numbers. Lock includes timeout (5s) and stale lock recovery.🔢 #5 — MCP create_invoice hardcoded sequence=1
File:
packages/mcp/src/tools/create-invoice.tsIntegrated the
create_invoiceMCP tool withSequenceStore.getNextSequence()so each invoice gets a unique, auto-incrementing sequence number. Previously every invoice would have clave/consecutivo ending in0000000001, causing 409 Conflict on submission.🔬 #8 — round5() floating-point precision
File:
packages/sdk/src/tax/calculator.tsReplaced
Math.round(value * 10^5) / 10^5withNumber(value.toFixed(5))to avoid IEEE 754 multiplication artifacts. For example,1.0000025 * 100000evaluates to100000.24999999999in JS, which would round incorrectly with the old approach.All 623 tests pass. Build succeeds across all 4 packages.