You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scripts/sync-leaderboard.js writes 7 JSON files during each sync cycle using fs.writeFileSync() — all in-place overwrite. If the process crashes mid-write (GitHub Actions timeout, OOM, runner killed), the file is left as truncated/invalid JSON. Since the data repo is the single source of truth, one corrupted file = permanent data loss.
This runs on a GitHub Actions cron schedule. Actions runners can be terminated at any moment with no graceful shutdown. A crash at line 194 (writing overall.json) wipes the entire leaderboard. Previous PRs (#60, #61, #65, #66) all focused on "use strict" and process.exit — none addressed write safety.
Root Cause — 7 Unsafe Write Locations
Every write follows this identical unsafe pattern (actual code at lines 193-197):
try{fs.writeFileSync(overallFilepath,JSON.stringify(overallData,null,2),"utf8",);console.log("Daily data saved successfully");}catch(err){console.error(`Failed to write json file: `,err.message);process.exit(1);}
The catch block runs too late — the file is already truncated the moment writeFileSync opens it for writing.
Complete list of all 7 unsafe write locations in the actual file:
Line(s)
File Written
Crash Impact
166
daily/<YYYY-MM-DD-D>.json
Lose 1 daily snapshot only
193-196
overall.json
Entire leaderboard data wiped
253
daily.json
Daily progress/rank change view breaks
309-312
weekly.json
Weekly progress/rank change view breaks
369-372
monthly.json
Monthly progress/rank change view breaks
430-444
changes.json
changelog lost (least critical)
456-466
last-sync.json
Sync timestamp lost (least critical)
Real Crash Scenario
GitHub Actions cron triggers sync-leaderboard.js
Script fetches new data for all 100+ users (~30 seconds)
At line 193, fs.writeFileSync begins writing overall.json — file is truncated
Runner gets preempted (common on free GitHub Actions) — process dies mid-write
overall.json now contains: [{ — invalid JSON, 200KB of data gone
Next sync runs: JSON.parse at line 185 throws — previousOverall becomes empty []
Rank change computation at line 391-425 produces empty diffs for ALL users
changes.json writes "no_changes": true every day forever — bug goes unnoticed
Proposed Solution
Step 1 — Add an atomicWrite helper above the IIFE (after line 118):
Step 3 — Add startup cleanup (insert at line 122, after DATA_DIR is defined):
// Clean up leftover tmp files from previous crashestry{consttmpFiles=fs.readdirSync(DATA_DIR).filter(f=>f.includes('.tmp.'));tmpFiles.forEach(f=>{constfilePath=path.join(DATA_DIR,f);fs.unlinkSync(filePath);console.log(`Cleaned up leftover tmp file: ${f}`);});}catch(err){console.warn("Failed to clean tmp files:",err.message);}
Also do the same for daily/, weekly/, monthly/ subdirectories.
Acceptance Criteria
atomicWrite() helper function exists and is used for all 7 write locations (lines 166, 193, 253, 309, 369, 430, 456)
Each write first creates a temp file, then renames — original file is never overwritten directly
If writeFileSync throws on the tmp file, the original file remains completely unchanged
JSON.stringify happens inside the helper — if it throws (circular ref, etc.), original file is safe
Startup cleanup removes any orphaned *.tmp.* files from the data directory and subdirectories
Output files are byte-for-byte identical to current output (no formatting change, no data loss)
Description
scripts/sync-leaderboard.jswrites 7 JSON files during each sync cycle usingfs.writeFileSync()— all in-place overwrite. If the process crashes mid-write (GitHub Actions timeout, OOM, runner killed), the file is left as truncated/invalid JSON. Since the data repo is the single source of truth, one corrupted file = permanent data loss.This runs on a GitHub Actions cron schedule. Actions runners can be terminated at any moment with no graceful shutdown. A crash at line 194 (writing
overall.json) wipes the entire leaderboard. Previous PRs (#60, #61, #65, #66) all focused on"use strict"andprocess.exit— none addressed write safety.Root Cause — 7 Unsafe Write Locations
Every write follows this identical unsafe pattern (actual code at lines 193-197):
The catch block runs too late — the file is already truncated the moment
writeFileSyncopens it for writing.Complete list of all 7 unsafe write locations in the actual file:
daily/<YYYY-MM-DD-D>.jsonoverall.jsondaily.jsonweekly.jsonmonthly.jsonchanges.jsonlast-sync.jsonReal Crash Scenario
sync-leaderboard.jsfs.writeFileSyncbegins writingoverall.json— file is truncatedoverall.jsonnow contains:[{— invalid JSON, 200KB of data goneJSON.parseat line 185 throws —previousOverallbecomes empty[]changes.jsonwrites"no_changes": trueevery day forever — bug goes unnoticedProposed Solution
Step 1 — Add an
atomicWritehelper above the IIFE (after line 118):Key details:
process.pid+Date.now()to avoid tmp filename collisions if multiple instances runrenameSyncis atomic on the same filesystem (POSIX guarantee, Windows NTFS guarantee)writeFileSyncthrows, original file is 100% intact — the tmp file is just garbage to clean upStep 2 — Replace all 7 writes:
Change every instance of:
To:
Step 3 — Add startup cleanup (insert at line 122, after DATA_DIR is defined):
Also do the same for
daily/,weekly/,monthly/subdirectories.Acceptance Criteria
atomicWrite()helper function exists and is used for all 7 write locations (lines 166, 193, 253, 309, 369, 430, 456)writeFileSyncthrows on the tmp file, the original file remains completely unchangedJSON.stringifyhappens inside the helper — if it throws (circular ref, etc.), original file is safe*.tmp.*files from the data directory and subdirectoriesnode --check scripts/sync-leaderboard.jsAffected Files
scripts/sync-leaderboard.js