How the Career-Ops Atomic Report Numbering Reservation System Prevents Conflicts

The Career-Ops atomic report numbering reservation system prevents conflicts by combining a centralized process lock, a unified view of existing report files and tracker entries, and atomic sentinel file creation using exclusive write flags, ensuring parallel workers never allocate duplicate IDs even under high concurrency.

The career-ops repository implements a robust atomic report numbering reservation system to coordinate parallel workers such as batch evaluators, scanners, and manual CLI invocations. This system guarantees that every report receives a unique sequential number by eliminating race conditions through three coordinated safety mechanisms that operate across the reserve-report-num.mjs, tracker-utils.mjs, and tracker-parse.mjs modules.

Centralized Locking with trackerLockDirFor

Before any reservation attempt, the process must acquire a shared tracker lock via acquireTrackerLock. This utility, defined in tracker-utils.mjs, is invoked within reserve-report-num.mjs at lines 85-86. The lock guarantees exclusive access while the reservation logic executes, preventing interleaved reads and writes from different processes that could otherwise lead to duplicate number assignments. Every component that writes to the tracker or creates report files must obtain this lock first, creating a single-writer bottleneck that serializes all reservation attempts.

Unified Occupancy Detection

The system constructs a complete set of already-used numbers by merging two distinct sources before attempting any allocation. The occupiedFromReports function (lines 71-79 in reserve-report-num.mjs) scans existing report files on disk, while occupiedFromTracker (lines 84-96) extracts referenced report numbers from tracker rows using parsers from tracker-parse.mjs. This merged view captures every number present in the filesystem or referenced in the tracker—including report links embedded inside tracker rows—creating a definitive inventory of unavailable IDs. By combining these sources, the system prevents collisions with legacy reports and ongoing tracker entries alike.

Atomic Sentinel File Creation

When the system identifies a free number, it writes a reservation sentinel file (e.g., 042-RESERVED.md) using writeFileSync with the O_CREAT|O_EXCL flag ({flag:'wx'}). Implemented in the claimSlot function at lines 19-24 of reserve-report-num.mjs, this atomic operation fails if another process has already created the same sentinel, converting potential race conditions into detectable filesystem errors. If any slot in a requested batch cannot be claimed, the entire reservation is aborted, previously claimed sentinels are released, and the algorithm retries with a newly calculated base range.

Retry Logic and Batch Reservation

The reserveReportNumbers function implements a retry loop with a maximum of 50 attempts. On each iteration, it:

  1. Calculates a starting base: base = highestNumber(occupied) + 1
  2. Attempts to claim a contiguous block of the requested size
  3. If any slot is already taken, releases partially claimed sentinels and recomputes: base = Math.max(failedAt + 1, highestNumber(occupied) + 1)

Because the tracker lock is held throughout the entire sequence, no other process can alter the occupancy set during the check-and-claim steps. This guarantees that the finally returned numbers are unique and conflict-free, even when dozens of parallel workers request batches simultaneously.

Garbage Collection for Stale Reservations

To prevent resource leaks from crashed processes, the gcStaleReportReservations function (lines 57-73 in reserve-report-num.mjs) removes stale sentinel files after a configurable TTL (default 4 hours). This cleanup operation also runs under the same tracker lock acquired via acquireTrackerLock, ensuring that removal never races with ongoing reservations and that reserved numbers eventually return to the available pool.

Practical Usage Examples

Reserve a Single Report Number

node reserve-report-num.mjs

# → prints e.g. 042

The CLI invokes reserveReportNumbers(1). The lock is taken, the next free number is discovered, a sentinel 042-RESERVED.md is created atomically, and the number is printed.

Reserve Multiple Consecutive Numbers

node reserve-report-num.mjs --count 5

# → prints e.g. 056-060

A batch of five contiguous IDs is reserved atomically. If any of the five slots were already taken, the reservation retries until a free range is found.

Release Reserved Numbers

node reserve-report-num.mjs --release 056-060

After workers finish writing the actual report files (e.g., reports/056-example.md), you free the sentinel files. The release operation also uses the same lock to avoid race conditions.

Programmatic API

import { reserveReportNumbers, releaseReportNumbers } from './reserve-report-num.mjs';

async function makeReports(count) {
  const nums = await reserveReportNumbers(count);
  // nums is an array like [101, 102, 103]
  // … generate reports using these numbers …
  // finally release the reservation tokens:
  await releaseReportNumbers(nums);
}

The exported async functions can be used by any mode (scan.mjs, batch workers, etc.). The returned array carries a hidden RESERVATION_TOKEN that must be passed to releaseReportNumbers unless you force the release.

Summary

  • Centralized locking via acquireTrackerLock ensures exclusive access during the reservation window (lines 85-86 in reserve-report-num.mjs).
  • Unified occupancy detection merges existing reports and tracker entries to identify unavailable numbers (lines 71-79 and 84-96).
  • Atomic sentinel creation with O_CREAT|O_EXCL flags prevents duplicate claims by converting races into filesystem errors (lines 19-24).
  • Automatic retry logic with up to 50 attempts handles transient conflicts by recalculating base numbers and releasing partial claims.
  • Garbage collection removes stale sentinels after 4 hours to prevent leaks from crashed workers (lines 57-73).

Frequently Asked Questions

What happens if two processes request report numbers simultaneously?

When two processes request numbers simultaneously, the centralized tracker lock ensures they execute sequentially. The first process acquires the lock via acquireTrackerLock, scans occupancy, and creates sentinel files atomically. The second process waits until the lock releases, then sees the updated occupancy set including the new sentinels, forcing it to select higher numbers.

How does the system handle crashed processes that never release their reservations?

The gcStaleReportReservations function runs periodically to remove sentinel files older than the configured TTL (default 4 hours). This garbage collection operates under the same tracker lock used for reservations, preventing cleanup from racing with active allocations and ensuring reserved numbers eventually return to the available pool.

Can I reserve non-consecutive report numbers?

The current implementation reserves contiguous blocks only. The reserveReportNumbers function calculates a base number and attempts to claim a consecutive range of the requested size. If you need scattered numbers, you must make multiple single-count reservations, though this reduces efficiency compared to batch allocation.

Where is the locking mechanism implemented?

The locking primitives reside in tracker-utils.mjs, which provides acquireTrackerLock and trackerLockDirFor utilities. The reservation logic that consumes these locks is located in reserve-report-num.mjs at lines 85-86, ensuring synchronized access to both the tracker state and the reservation sentinels.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →