claude-usage-limits 1.39.5 → 1.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "usage-limits",
3
3
  "displayName": "Usage Limits",
4
- "version": "1.39.5",
4
+ "version": "1.40.1",
5
5
  "description": "Puts your remaining Claude Code usage limit into Claude's context before every prompt, so it opens with what fits in the budget instead of starting work that gets cut off. Reports headroom as turns rather than percentages, prices a job before you start it, and detects your plan tier.",
6
6
  "author": {
7
7
  "name": "Ridelink",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usage-limits",
3
- "version": "1.39.5",
3
+ "version": "1.40.1",
4
4
  "description": "Reports how much of your Codex usage limit is left as turns of work rather than a percentage, prices a job before you start it, and counts the other agents sharing the same budget.",
5
5
  "author": {
6
6
  "name": "Ridelink",
package/README.md CHANGED
@@ -350,6 +350,106 @@ The skill also requires Claude to say up front when a job will not fit in what
350
350
  is left, name what it is doing now, what it is leaving, and when the rest can
351
351
  happen, rather than starting and stopping halfway through an edit.
352
352
 
353
+ ## /low-priority, and Claude Code's own features at the wall
354
+
355
+ Claude Code has grown its own machinery for the moment the 5-hour limit is hit.
356
+ Some of it a hook can read and some of it cannot, and the whole of this
357
+ section is about keeping that line straight — a plugin that guesses at the half
358
+ it cannot see is worse at this than one that says nothing.
359
+
360
+ **`/low-priority`** is a hidden toggle the CLI offers when the session (5-hour)
361
+ limit is reached. It keeps the session working at reduced priority and spends
362
+ your **weekly** limit, plus a separate weekly lower-priority allowance. Replies
363
+ can pause while it waits for spare capacity. It appears in no changelog and in
364
+ no documentation — there are zero mentions across all 405 versions of the
365
+ bundled changelog back to 0.2.21 — and it is declared `isHidden: true`, so the
366
+ only way anyone learns of it is the offer line at the wall.
367
+
368
+ What the plugin can read is whether **your account is provisioned** for it, from
369
+ one field in the same `~/.claude.json` it already parses for the meter:
370
+ `cachedGrowthBookFeatures.tengu_toasty_breeze`. That costs no extra I/O, and it
371
+ is re-read on every prompt rather than remembered, because the grant can be
372
+ withdrawn mid-week.
373
+
374
+ What the plugin **cannot** read is whether it is on right now. That state lives
375
+ in the CLI's process memory and is written to no file — not `~/.claude.json`,
376
+ not `~/.claude/state`, and no hook payload or status-line field carries it. So
377
+ there are three states and never a fourth:
378
+
379
+ | state | how it is known |
380
+ | --- | --- |
381
+ | **absent** | the account is not provisioned, and the plugin never mentions the command |
382
+ | **offered** | provisioned, so the brief says it exists at the wall — as a possibility |
383
+ | **acknowledged** | **you** said you switched it on: `mode --low-priority on` |
384
+
385
+ Two further gates sit in front of the offer that nothing here will ever see: the
386
+ experiment arm arrives in a response header, and the CLI withholds the offer
387
+ during a cooloff and once the weekly allowance is spent. So the brief says "if
388
+ the wall offers it", never "you can run it". And it never says the plugin or the
389
+ model can switch it on: Claude cannot type a slash command, and the command is
390
+ declared `supportsNonInteractive: false`, so it is unusable in a headless or
391
+ relayed run either.
392
+
393
+ At the wall with weekly headroom, the line looks like this:
394
+
395
+ ```
396
+ If the wall offers it, /low-priority carries this session past the 5-hour limit
397
+ at lower priority instead of stopping: it spends the weekly limit, which is at
398
+ 45% and so has room, and replies may pause while it waits for spare capacity -
399
+ the wait and its ceiling are set by the server per request. It is a toggle: you
400
+ type it yourself and run it again to stop, and nothing here can switch it on
401
+ for you.
402
+ ```
403
+
404
+ **The recommendation has a number behind it.** It is offered only while the
405
+ weekly is at or below **80 per cent** and the 5-hour window is the binding wall.
406
+ Above that the brief actively says not to, and cites the figure: low-priority
407
+ spends the weekly *and* draws on a weekly allowance whose size is exposed to no
408
+ hook and no file, and a real user measured it emptying most of a week in a
409
+ couple of hours. No wait time is ever printed, because the retry and the ceiling
410
+ come from `lowPriorityRetryAfterSeconds` and `lowPriorityMaxWaitSeconds` on each
411
+ response — any fixed "20 seconds, 20 minutes" would be invented.
412
+
413
+ Once you acknowledge it, three things change. The brief brakes on the **weekly**
414
+ window instead of the 5-hour one, because that is the only brake left. It stops
415
+ telling you to wind down at the 5-hour wall. And the relay **will not book a
416
+ wake for the 5-hour reset** — this session carries straight past it, so a wake
417
+ would fire into a session that never stopped. A weekly wall still arms. The
418
+ acknowledgement lapses by itself when the 5-hour window it was made against
419
+ resets.
420
+
421
+ **The graceful wrap-up note.** Claude Code can inject its own "finish up"
422
+ instruction at the wall. The mechanism is real, and the gate is
423
+ `tengu_lantern_wick_mode` — the bundle's own normalizer keeps only `"wrap-up"`
424
+ and `"next-steps"` and maps everything else to `"off"`. When it is on, this
425
+ plugin stands down and says who is speaking, because two agents telling the
426
+ model to wind down in different words is worse than one. When it is off, the
427
+ plugin's own instruction stands. It is read, not assumed, so the right thing
428
+ happens whichever way the flag is set. (The near-limit variant is a separate
429
+ flag, `tengu_vellum_anchor`.)
430
+
431
+ **`autoContinueAtUsageLimit`.** Since 2.1.234 Claude Code waits out the reset
432
+ and continues the same open session by itself, and the setting is **on by
433
+ default**. In place, with the context intact, that is better than any scheduled
434
+ wake. So when a resume relay is armed and this is on, the brief says so: the
435
+ wake is the route for a session that will be **closed** at the reset, and both
436
+ firing for the same reset would start the work twice and spend the weekly twice.
437
+
438
+ **`/limit-reset`**, the once-weekly manual session reset, is detected but not
439
+ built on: this account holds no grant (`tengu_cedar_ember` absent,
440
+ `cachedUsageUtilization.cedar_ember` null), so a feature resting on it would be
441
+ untestable. If a grant appears, the brief names it at the wall, says it only
442
+ works while you are actually at a limit, and says the work it unlocks still
443
+ spends the weekly. It never reports how many are left — `resets_left` comes from
444
+ a live endpoint and appears in no file a hook can read.
445
+
446
+ **Grace is not weekly spend.** The allowance the wall gives you is metered in
447
+ its own per-window meters (`anthropic-ratelimit-unified-grace-5h-utilization`
448
+ and `-grace-7d-utilization`, with a `rateLimitGraceZone` naming which window it
449
+ belongs to), not billed to the 7-day window. Its size arrives in response
450
+ headers and appears in no hook payload, no status-line field and no file, so the
451
+ plugin says nothing about how much of it is left rather than estimating.
452
+
353
453
  ## The relay: carrying a project across the reset
354
454
 
355
455
  The handoff has always had the same flaw. It gets written, and then it sits in
@@ -1170,7 +1270,8 @@ skills/usage-limits/scripts/ usage.js, brief.js, pulse.js, stop.js,
1170
1270
  recommend.js, panel.js, feed.js,
1171
1271
  statusline.js, live.js, view.js, bars.js,
1172
1272
  activity.js, reading.js, drift.js,
1173
- mode.js, voice.js, relay.js, wake.js
1273
+ mode.js, voice.js, relay.js, wake.js,
1274
+ lowpri.js
1174
1275
  skills/usage-limits/references/ the longer notes
1175
1276
  hooks/hooks.json runs brief.js before each prompt, pulse.js
1176
1277
  during long turns, stop.js after each reply
@@ -1182,6 +1283,7 @@ commands/statusline.md the /usage-limits:statusline command
1182
1283
  commands/usage-mode.md the /usage-mode command
1183
1284
  bin/cli.js the npx entry point
1184
1285
  tools/sync-version.js keeps the manifest version in step
1286
+ tools/test-tempdirs.js temp directories the suite deletes on exit
1185
1287
  vscode/ the VS Code extension; build.js copies the
1186
1288
  scripts into vscode/lib and makes the vsix
1187
1289
  test/ node --test, no dependencies
@@ -1193,10 +1295,11 @@ test/ node --test, no dependencies
1193
1295
  node --test
1194
1296
  ```
1195
1297
 
1196
- 712 tests over the pricing, the window arithmetic, plan and credit detection,
1298
+ 895 tests over the pricing, the window arithmetic, plan and credit detection,
1197
1299
  the status line, the before-prompt line, the mid-turn pulse, the after-reply tally and the session history, job forecasting,
1198
1300
  per-project attribution, the Codex reader and its installer, the CLI,
1199
- packaging, and the settings save/restore.
1301
+ packaging, the settings save/restore, and what Claude Code's own wall-time
1302
+ features are readable as (/low-priority, the wrap-up note, autoContinueAtUsageLimit).
1200
1303
 
1201
1304
  The budget modes are checked as rules rather than examples: the whole stopping
1202
1305
  matrix is walked in every mode (640 lines), `off` must inject nothing at any
@@ -55,6 +55,29 @@ Commands:
55
55
  the last one, naming it first.
56
56
  - `--ledger` - measured turns and cost per turn, per mode, from what replies
57
57
  actually cost.
58
+ - `--low-priority` / `--low-priority on` / `--low-priority off` - whether YOU
59
+ have switched Claude Code's `/low-priority` on. With no argument it reports
60
+ what is recorded, whether this account is provisioned for the command at all,
61
+ whether Claude Code will inject its own wrap-up note at the wall on this
62
+ machine, and whether the CLI's own continue-after-reset is on.
63
+
64
+ **This is a record of something the user did, never a reading.** Claude Code
65
+ keeps the live low-priority state in process memory and writes it to no file:
66
+ not `~/.claude.json`, not `~/.claude/state`, and no hook payload or
67
+ status-line field carries it. So the plugin knows three things and not a
68
+ fourth - the account is provisioned (`tengu_toasty_breeze.enabled`), the user
69
+ has said it is on, or neither. It never claims it is running.
70
+
71
+ `on` changes three behaviours: the brief brakes on the **weekly** window
72
+ instead of the 5-hour one, it stops telling you to wind down at the 5-hour
73
+ wall, and the relay will not book a wake for the 5-hour reset - that reset is
74
+ no longer a wall this session stops at. The record lapses on its own when the
75
+ 5-hour window it was made against resets, because that is when low-priority
76
+ ends. Say `off` if it ends sooner.
77
+
78
+ The model cannot type a slash command, and `/low-priority` is declared
79
+ `supportsNonInteractive: false`, so it is also unusable in a headless or
80
+ relayed run. Nothing here can switch it on.
58
81
 
59
82
  Two rules that hold in every mode:
60
83
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-usage-limits",
3
- "version": "1.39.5",
3
+ "version": "1.40.1",
4
4
  "description": "Puts your remaining Claude Code usage limit into Claude's context before every prompt, so it opens with what fits in the budget instead of starting work that gets cut off. Reports headroom as turns rather than percentages, prices a job before you start it, and detects your plan tier.",
5
5
  "keywords": [
6
6
  "claude",
@@ -591,6 +591,49 @@ was not caution; it was quitting with a reason that sounded like one.
591
591
  Only when the switch is genuinely unavailable - no other window has room, or the
592
592
  user has ruled it out - does the checkpoint below apply.
593
593
 
594
+ ### /low-priority: the user's lever, never yours
595
+
596
+ Claude Code has a hidden toggle, `/low-priority`, offered when the 5-hour session
597
+ limit is reached. It keeps the session working at reduced priority and spends the
598
+ **weekly** limit, plus a separate weekly lower-priority allowance; replies can
599
+ pause while it waits for spare capacity.
600
+
601
+ **You cannot switch it on.** You cannot type a slash command, and the command is
602
+ declared `supportsNonInteractive: false`, so it does not work in a headless or
603
+ relayed run either. What you may do is say, in one line, that it exists and what
604
+ it costs - and only when the budget line has already said so, because the line is
605
+ the only thing that knows whether this account is provisioned for it at all.
606
+ Never volunteer it otherwise: telling a user to run a command their account does
607
+ not have is worse than saying nothing.
608
+
609
+ Three rules when the line does mention it:
610
+
611
+ - **Word it as a possibility.** The offer is gated on an experiment arm that
612
+ arrives in a response header, and the CLI also withholds it during a cooloff
613
+ and once the weekly allowance is spent. "If the wall offers it", not "run it".
614
+ - **Never invent a wait time.** The retry interval and its ceiling come from the
615
+ server on each response. There is no fixed number.
616
+ - **If the line says not to, say not to, and why.** Above 80 per cent weekly it
617
+ is the wrong move: it spends the window that takes days to come back in order
618
+ to save one that comes back in hours.
619
+
620
+ If the user tells you they have switched it on, the honest answer is to record
621
+ it: `node scripts/mode.js --low-priority on`. From then the budget line brakes on
622
+ the weekly instead of the 5-hour window, stops telling you to wind down at the
623
+ 5-hour wall, and the relay stops booking a wake for a reset this session no
624
+ longer stops at. **Record it only when they said so.** Whether it is running is
625
+ in the CLI's process memory and readable nowhere, so nothing here may infer it,
626
+ and `mode --low-priority off` is how it is taken back.
627
+
628
+ The same applies to `/limit-reset`, the once-weekly manual session reset: the
629
+ line names it only if a grant is actually readable, it only works while you are
630
+ at a limit, the work it unlocks still spends the weekly, and how many are left is
631
+ not knowable from here.
632
+
633
+ And if the budget line says Claude Code is injecting its own wrap-up note at this
634
+ wall, follow that note: it is more specific than anything here, and two
635
+ instructions to wind down in different words is worse than one.
636
+
594
637
  ## 6. Checkpoint before the wall
595
638
 
596
639
  When the binding window is under roughly 10 percent, or under about ten turns
@@ -865,7 +908,7 @@ stop.
865
908
  | `scripts/host.js` | Works out which agent this is running inside, so one host's percentages are never reported against the other's turns. |
866
909
  | `scripts/codex.js` | The Codex reader: the meter and the pace out of `~/.codex/sessions`, plus the live `--refresh` call. |
867
910
  | `scripts/install-codex-hook.js` | `status`, `on`, `off`. Installs the Codex-side instruction, which Claude Code does not need. |
868
- | `scripts/mode.js` | The budget mode: no arguments to report it, `max`/`high`/`standard`/`off` to set it, `auto`, `off --guard 95`, `--list`, `--explain <name>`, `--floor`/`--ceiling`/`--pin`, `--baseline`, `--advice`/`--no-advice`, `--history`, `undo`, `--ledger`. Reads settings.json and never writes it. |
911
+ | `scripts/mode.js` | The budget mode: no arguments to report it, `max`/`high`/`standard`/`off` to set it, `auto`, `off --guard 95`, `--list`, `--explain <name>`, `--floor`/`--ceiling`/`--pin`, `--baseline`, `--advice`/`--no-advice`, `--history`, `undo`, `--ledger`, `--low-priority [on|off]`. Reads settings.json and never writes it. |
869
912
  | `scripts/lowpower.js` | `status`, `on`, `off`. Restores what it replaced. Claude Code only. |
870
913
  | `scripts/recommend.js` | The chooser behind `usage.js --recommend`: posture, then the effort and model commands for each lever. Not meant to be called by hand. |
871
914
  | `references/tactics.md` | Every lever that lowers cost, and why it works. |
@@ -879,6 +922,7 @@ stop.
879
922
  | `scripts/net.js` | Can this machine reach the API, and was a failed run the network's fault. Three probes, TLS-interception detection, and the backoff the offline retries use. |
880
923
  | `scripts/wake.js` | What the scheduler runs after the reset: re-checks the meter, then notifies or resumes. Never called by hand. |
881
924
  | `scripts/voice.js` | The local writing profile: `show`, `card`, `set "<instruction>"`, `clear`, `off`/`on`, `forget`. |
925
+ | `scripts/lowpri.js` | What Claude Code's own features at the wall are readable as: whether this account is provisioned for `/low-priority`, whether a manual session reset grant exists, whether the CLI injects its own wrap-up note here, whether usage credits are available, whether `autoContinueAtUsageLimit` is on, and the record of the user saying they switched low-priority on. Reads only; never writes anything but that one record. |
882
926
  | `references/how-it-works.md` | Where the numbers come from and where they are soft. |
883
927
 
884
928
  ## Codex controls
@@ -14,6 +14,8 @@ const fs = require('fs');
14
14
  const os = require('os');
15
15
  const path = require('path');
16
16
 
17
+ const atomic = require('./atomic.js');
18
+
17
19
  const KEEP_SESSIONS = 8;
18
20
  // A session that has said nothing for this long is not working, whatever its
19
21
  // last word was: a crash never sends Stop.
@@ -68,22 +70,8 @@ function mark(state, sessionId, extra, now) {
68
70
  if (extra && extra.model) entry.model = String(extra.model);
69
71
  else if (previous.model) entry.model = previous.model;
70
72
  all[sessionId || '_'] = entry;
71
- const file = activityFile();
72
- const temp = file + '.' + process.pid + '.usage-limits-tmp';
73
- try {
74
- fs.mkdirSync(path.dirname(file), { recursive: true });
75
- fs.writeFileSync(temp, JSON.stringify(trim(all)), 'utf8');
76
- fs.renameSync(temp, file);
77
- } catch (err) {
78
- // A rename Windows refused leaves nothing behind.
79
- try {
80
- fs.unlinkSync(temp);
81
- } catch (gone) {
82
- // Nothing to clean up.
83
- }
84
- return false;
85
- }
86
- return true;
73
+ // A rename Windows refused leaves nothing behind: see atomic.js.
74
+ return atomic.tryWriteFileAtomic(activityFile(), JSON.stringify(trim(all)));
87
75
  } catch (err) {
88
76
  return false;
89
77
  }
@@ -0,0 +1,173 @@
1
+ 'use strict';
2
+
3
+ // Every file this plugin replaces goes through here: written beside the target
4
+ // under a name that is this process's own, then renamed into place, so a
5
+ // reader sees either the old whole file or the new whole file.
6
+ //
7
+ // On Windows that rename is the step that fails. Two processes renaming onto
8
+ // the same target at the same moment - the prompt hook, the pulse and the
9
+ // status line do exactly that - get EPERM back from MoveFileEx for the one
10
+ // that loses. Measured on 2026-09-25 with four processes writing one file for
11
+ // three seconds: 2109 renames refused with EPERM against 469 that went
12
+ // through. The writers that did not delete their temporary file on that
13
+ // failure left one behind every time, and about seventy of them had piled up
14
+ // in ~/.claude by then. The same retry on EPERM, EACCES and EBUSY is what
15
+ // graceful-fs does for npm on Windows; here it is bounded tightly, because
16
+ // the hooks that call this have ten seconds between them and the prompt.
17
+ //
18
+ // What no code path can clean is a process killed between creating its
19
+ // temporary file and renaming it - a hook that ran into its timeout. sweep()
20
+ // removes those at the next prompt, once they are old enough that no live
21
+ // writer can still own them.
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+
26
+ // One suffix for every temporary file, and nobody else's: anything ending in
27
+ // it is this plugin's, so the sweep never has to guess.
28
+ const SUFFIX = '.usage-limits-tmp';
29
+
30
+ // The codes Windows returns while another process has the target, or is
31
+ // replacing it. Anything else - ENOSPC, ENOENT, EISDIR - will not change on a
32
+ // second try.
33
+ const RETRY_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);
34
+
35
+ // About a third of a second in all. Contention clears in milliseconds; a
36
+ // target that is still held after this is held by something that will not let
37
+ // go soon, and the caller carries on with what is on disk.
38
+ const RETRY_DELAYS_MS = [5, 10, 20, 40, 60, 80, 100];
39
+
40
+ // A write takes milliseconds and the longest hook is killed at ten seconds, so
41
+ // nothing alive owns a temporary file this old.
42
+ const STALE_MS = 10 * 60 * 1000;
43
+
44
+ // Temporary names this plugin has used. The first is the one written now; the
45
+ // second is what reading.js, drift.js, mode.js and relay.js wrote before
46
+ // 1.39.6 - `usage-limits-<name>.json.<pid>.tmp` - and is matched only with the
47
+ // usage-limits- prefix and the pid, because a bare .tmp could be anyone's.
48
+ const OWN_TEMP = [
49
+ /^.+\.usage-limits-tmp$/,
50
+ /^usage-limits-[A-Za-z0-9_-]+\.json\.\d+\.tmp$/,
51
+ ];
52
+
53
+ function sleepSync(ms) {
54
+ try {
55
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
56
+ } catch (err) {
57
+ // No shared memory here: spin for the same time instead.
58
+ const until = Date.now() + ms;
59
+ while (Date.now() < until) {
60
+ // Waiting.
61
+ }
62
+ }
63
+ }
64
+
65
+ function tempFor(file) {
66
+ return file + '.' + process.pid + SUFFIX;
67
+ }
68
+
69
+ function renameWithRetry(from, to) {
70
+ for (let attempt = 0; ; attempt += 1) {
71
+ try {
72
+ fs.renameSync(from, to);
73
+ return;
74
+ } catch (err) {
75
+ if (!RETRY_CODES.has(err && err.code) || attempt >= RETRY_DELAYS_MS.length) throw err;
76
+ sleepSync(RETRY_DELAYS_MS[attempt]);
77
+ }
78
+ }
79
+ }
80
+
81
+ // Never throws. True when the file is gone, whoever removed it.
82
+ function removeQuietly(file) {
83
+ for (let attempt = 0; ; attempt += 1) {
84
+ try {
85
+ fs.unlinkSync(file);
86
+ return true;
87
+ } catch (err) {
88
+ if (err && err.code === 'ENOENT') return true;
89
+ if (!RETRY_CODES.has(err && err.code) || attempt >= 3) return false;
90
+ sleepSync(RETRY_DELAYS_MS[attempt]);
91
+ }
92
+ }
93
+ }
94
+
95
+ // Throws what the write or the last rename threw, after removing the
96
+ // temporary file. `options` is passed to writeFileSync (encoding, mode).
97
+ function writeFileAtomic(file, text, options) {
98
+ const temp = tempFor(file);
99
+ try {
100
+ fs.mkdirSync(path.dirname(file), { recursive: true });
101
+ fs.writeFileSync(temp, text, options === undefined ? 'utf8' : options);
102
+ renameWithRetry(temp, file);
103
+ } catch (err) {
104
+ removeQuietly(temp);
105
+ throw err;
106
+ }
107
+ }
108
+
109
+ // The same, for callers that carry on either way.
110
+ function tryWriteFileAtomic(file, text, options) {
111
+ try {
112
+ writeFileAtomic(file, text, options);
113
+ return true;
114
+ } catch (err) {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ function isOwnTemp(name) {
120
+ return OWN_TEMP.some((pattern) => pattern.test(name));
121
+ }
122
+
123
+ // Removes this plugin's temporary files older than STALE_MS from each
124
+ // directory. Only names isOwnTemp() accepts are ever looked at. Never throws;
125
+ // returns how many were removed.
126
+ function sweep(dirs, now, staleMs) {
127
+ const at = Number.isFinite(now) ? now : Date.now();
128
+ const age = Number.isFinite(staleMs) ? staleMs : STALE_MS;
129
+ let removed = 0;
130
+ const seen = new Set();
131
+ for (const dir of [].concat(dirs || [])) {
132
+ if (!dir || seen.has(path.resolve(dir))) continue;
133
+ seen.add(path.resolve(dir));
134
+ let names;
135
+ try {
136
+ names = fs.readdirSync(dir);
137
+ } catch (err) {
138
+ continue;
139
+ }
140
+ for (const name of names) {
141
+ if (!isOwnTemp(name)) continue;
142
+ const file = path.join(dir, name);
143
+ try {
144
+ const stat = fs.statSync(file);
145
+ if (!stat.isFile() || at - stat.mtimeMs < age) continue;
146
+ } catch (err) {
147
+ continue;
148
+ }
149
+ try {
150
+ fs.unlinkSync(file);
151
+ removed += 1;
152
+ } catch (err) {
153
+ // Gone already is someone else's sweep; held is worth one more try.
154
+ if (err && err.code !== 'ENOENT' && removeQuietly(file)) removed += 1;
155
+ }
156
+ }
157
+ }
158
+ return removed;
159
+ }
160
+
161
+ module.exports = {
162
+ SUFFIX,
163
+ STALE_MS,
164
+ RETRY_CODES,
165
+ RETRY_DELAYS_MS,
166
+ tempFor,
167
+ renameWithRetry,
168
+ removeQuietly,
169
+ writeFileAtomic,
170
+ tryWriteFileAtomic,
171
+ isOwnTemp,
172
+ sweep,
173
+ };