claude-task-worker 0.142.2 → 0.142.4

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.
@@ -2140,9 +2140,9 @@ function generateWorktreeName() {
2140
2140
  }
2141
2141
 
2142
2142
  // src/slack.ts
2143
- import { exec } from "node:child_process";
2143
+ import { exec, execFile as execFile2 } from "node:child_process";
2144
2144
  import { readFileSync as readFileSync2, renameSync as renameSync3, writeFileSync as writeFileSync4 } from "node:fs";
2145
- import { homedir as homedir3 } from "node:os";
2145
+ import { homedir as homedir3, tmpdir } from "node:os";
2146
2146
  import { join as join3 } from "node:path";
2147
2147
  import { promisify as promisify2 } from "node:util";
2148
2148
 
@@ -2233,10 +2233,13 @@ function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
2233
2233
 
2234
2234
  // src/slack.ts
2235
2235
  var execAsync = promisify2(exec);
2236
+ var execFileAsync2 = promisify2(execFile2);
2236
2237
  var WEBHOOK_URL = process.env.CLAUDE_TASK_WORKER_SLACK_WEBHOOK_URL;
2237
2238
  var USAGE_POLL_INTERVAL_SECONDS = 600;
2238
2239
  var USAGE_BACKOFF_PATH = "/tmp/claude-usage-backoff.json";
2239
2240
  var USAGE_RATE_LIMIT_DEFAULT_BACKOFF_SECONDS = 600;
2241
+ var USAGE_CACHE_PATH = "/tmp/claude-usage-cache.json";
2242
+ var USAGE_CACHE_TTL_SECONDS = 600;
2240
2243
  async function send(payload) {
2241
2244
  if (!WEBHOOK_URL) return;
2242
2245
  try {
@@ -2297,38 +2300,66 @@ function writeBackoffUntil(until) {
2297
2300
  } catch {
2298
2301
  }
2299
2302
  }
2303
+ function readUsageCache(now = Date.now()) {
2304
+ try {
2305
+ const { fetchedAt, usage } = JSON.parse(readFileSync2(USAGE_CACHE_PATH, "utf-8"));
2306
+ return now - Number(fetchedAt) < USAGE_CACHE_TTL_SECONDS * 1e3 ? usage : null;
2307
+ } catch {
2308
+ return null;
2309
+ }
2310
+ }
2311
+ function writeUsageCache(usage) {
2312
+ try {
2313
+ const tmp = `${USAGE_CACHE_PATH}.${process.pid}.tmp`;
2314
+ writeFileSync4(tmp, JSON.stringify({ fetchedAt: Date.now(), usage }));
2315
+ renameSync3(tmp, USAGE_CACHE_PATH);
2316
+ } catch {
2317
+ }
2318
+ }
2300
2319
  async function fetchUsageInfo() {
2301
2320
  const backoffUntil = readBackoffUntil();
2302
2321
  if (Date.now() < backoffUntil) {
2303
2322
  console.error(`[slack] Usage API rate limited; skipping until ${new Date(backoffUntil).toISOString()}`);
2304
2323
  return null;
2305
2324
  }
2325
+ const cached = readUsageCache();
2326
+ if (cached) return cached;
2306
2327
  try {
2307
- const envToken = process.env.CLAUDE_CODE_OAUTH_TOKEN?.trim();
2308
- if (envToken) {
2309
- const res = await requestUsage(envToken);
2310
- if (res.status !== 401 && res.status !== 403) return await handleUsageResponse(res);
2311
- console.error(
2312
- `[slack] Usage API rejected CLAUDE_CODE_OAUTH_TOKEN (${res.status}); falling back to stored credentials`
2313
- );
2314
- }
2315
- const { token, expiresAt } = await getStoredCredential();
2328
+ let { token, expiresAt } = await getStoredCredential();
2316
2329
  if (!token) {
2317
- console.error("[slack] No OAuth token found (set CLAUDE_CODE_OAUTH_TOKEN or sign in to Claude Code)");
2330
+ console.error("[slack] No OAuth token found (sign in to Claude Code)");
2318
2331
  return null;
2319
2332
  }
2320
- if (expiresAt !== void 0 && expiresAt <= Date.now()) {
2333
+ if (isExpired(expiresAt)) {
2321
2334
  console.error(
2322
- `[slack] OAuth token expired at ${new Date(expiresAt).toISOString()} (it is refreshed when claude runs)`
2335
+ `[slack] OAuth token expired at ${new Date(expiresAt).toISOString()}; running claude to refresh it`
2323
2336
  );
2324
- return null;
2337
+ await refreshTokenByRunningClaude();
2338
+ ({ token, expiresAt } = await getStoredCredential());
2339
+ if (!token || isExpired(expiresAt)) {
2340
+ console.error("[slack] OAuth token is still expired after running claude");
2341
+ return null;
2342
+ }
2325
2343
  }
2326
- return await handleUsageResponse(await requestUsage(token));
2344
+ const usage = await handleUsageResponse(await requestUsage(token));
2345
+ if (usage) writeUsageCache(usage);
2346
+ return usage;
2327
2347
  } catch (err) {
2328
2348
  console.error(`[slack] Failed to fetch usage info: ${err}`);
2329
2349
  return null;
2330
2350
  }
2331
2351
  }
2352
+ function isExpired(expiresAt) {
2353
+ return expiresAt !== void 0 && expiresAt <= Date.now();
2354
+ }
2355
+ async function refreshTokenByRunningClaude() {
2356
+ const { CLAUDE_CODE_OAUTH_TOKEN: _ignored, ...env } = process.env;
2357
+ try {
2358
+ await execFileAsync2("claude", ["-p", "/exit"], { cwd: tmpdir(), env, timeout: 12e4 });
2359
+ } catch (err) {
2360
+ console.error(`[slack] Failed to run claude for token refresh: ${err}`);
2361
+ }
2362
+ }
2332
2363
  function requestUsage(token) {
2333
2364
  return fetch("https://api.anthropic.com/api/oauth/usage", {
2334
2365
  headers: {
@@ -2450,7 +2481,7 @@ import { promisify as promisify3 } from "node:util";
2450
2481
  import { rm, stat } from "node:fs/promises";
2451
2482
  import { basename as basename2, resolve as resolve2, sep } from "node:path";
2452
2483
  var childProcess2 = createRequire2(import.meta.url)("node:child_process");
2453
- var execFileAsync2 = (command, args) => promisify3(childProcess2.execFile)(command, args);
2484
+ var execFileAsync3 = (command, args) => promisify3(childProcess2.execFile)(command, args);
2454
2485
  var WORKTREES_DIR = ".claude/worktrees";
2455
2486
  function isManagedWorktreePath(path2) {
2456
2487
  return resolve2(path2).startsWith(resolve2(WORKTREES_DIR) + sep);
@@ -2477,7 +2508,7 @@ async function forceRemoveIfExists(path2) {
2477
2508
  }
2478
2509
  }
2479
2510
  async function listWorktreeEntries() {
2480
- const { stdout } = await execFileAsync2("git", ["worktree", "list", "--porcelain"]);
2511
+ const { stdout } = await execFileAsync3("git", ["worktree", "list", "--porcelain"]);
2481
2512
  return stdout.replace(/\r\n/g, "\n").trim().split("\n\n").filter(Boolean).map((entry) => {
2482
2513
  const lines = entry.split("\n");
2483
2514
  const worktreeLine = lines.find((l) => l.startsWith("worktree "));
@@ -2491,7 +2522,7 @@ async function listWorktreeEntries() {
2491
2522
  }
2492
2523
  async function removeRegisteredWorktree(worktreePath) {
2493
2524
  try {
2494
- await execFileAsync2("git", ["worktree", "remove", "--force", "--force", worktreePath]);
2525
+ await execFileAsync3("git", ["worktree", "remove", "--force", "--force", worktreePath]);
2495
2526
  } catch (error) {
2496
2527
  console.error(`[worktree] Failed to remove worktree ${worktreePath}:`, error);
2497
2528
  }
@@ -2500,7 +2531,7 @@ async function removeRegisteredWorktree(worktreePath) {
2500
2531
  var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["main", "master", "develop"]);
2501
2532
  async function getDefaultBranchName() {
2502
2533
  try {
2503
- const { stdout } = await execFileAsync2("git", ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
2534
+ const { stdout } = await execFileAsync3("git", ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
2504
2535
  return stdout.trim().replace(/^origin\//, "");
2505
2536
  } catch {
2506
2537
  return void 0;
@@ -2513,7 +2544,7 @@ async function deleteLocalBranch(branchName) {
2513
2544
  return;
2514
2545
  }
2515
2546
  try {
2516
- await execFileAsync2("git", ["branch", "-D", branchName]);
2547
+ await execFileAsync3("git", ["branch", "-D", branchName]);
2517
2548
  console.log(`[worktree] Deleted local branch: ${branchName}`);
2518
2549
  } catch (error) {
2519
2550
  const stderr = String(error?.stderr ?? "");
@@ -2523,7 +2554,7 @@ async function deleteLocalBranch(branchName) {
2523
2554
  }
2524
2555
  async function localBranchExists(branchName) {
2525
2556
  try {
2526
- await execFileAsync2("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${branchName}`]);
2557
+ await execFileAsync3("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${branchName}`]);
2527
2558
  return true;
2528
2559
  } catch {
2529
2560
  return false;
@@ -2541,7 +2572,7 @@ function createWorktreeFromBranch(worktreeId, baseBranch) {
2541
2572
  }
2542
2573
  async function addWorktree(worktreeId, baseBranch) {
2543
2574
  const worktreePath = getWorktreePath(worktreeId);
2544
- await execFileAsync2("git", ["worktree", "add", "--track", "-B", worktreeId, worktreePath, `origin/${baseBranch}`]);
2575
+ await execFileAsync3("git", ["worktree", "add", "--track", "-B", worktreeId, worktreePath, `origin/${baseBranch}`]);
2545
2576
  }
2546
2577
  async function removeWorktree(worktreeId) {
2547
2578
  const worktreePath = getWorktreePath(worktreeId);
@@ -2987,7 +3018,7 @@ function createPrPollingWorker(config) {
2987
3018
  import { createRequire as createRequire3 } from "node:module";
2988
3019
  import { promisify as promisify4 } from "node:util";
2989
3020
  var childProcess3 = createRequire3(import.meta.url)("node:child_process");
2990
- var execFileAsync3 = (command, args) => promisify4(childProcess3.execFile)(command, args);
3021
+ var execFileAsync4 = (command, args) => promisify4(childProcess3.execFile)(command, args);
2991
3022
  var CONFIG_FILE = "claude-task-worker.json";
2992
3023
  var LABEL_TRIAGE_SCOPE2 = "cc-triage-scope";
2993
3024
  function lastRunBranchName(workerName) {
@@ -3010,7 +3041,7 @@ function lastRunPrBody(workerName, at) {
3010
3041
  ].join("\n");
3011
3042
  }
3012
3043
  async function git(cwd, args) {
3013
- const { stdout } = await execFileAsync3("git", ["-C", cwd, ...args]);
3044
+ const { stdout } = await execFileAsync4("git", ["-C", cwd, ...args]);
3014
3045
  return stdout;
3015
3046
  }
3016
3047
  async function publishLastRunPr(workerName, defaultBranch, at) {
package/dist/index.js CHANGED
@@ -65,7 +65,7 @@ import {
65
65
  shutdown,
66
66
  waitForAllProcesses,
67
67
  writeLastRun
68
- } from "./chunk-EYML7T7A.js";
68
+ } from "./chunk-WTY7GTRT.js";
69
69
  import {
70
70
  UserConfigError,
71
71
  assertCloudCompatibleCommand,
package/dist/lib.js CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  removeWorktreeByBranch,
56
56
  run,
57
57
  syncDefaultBranch
58
- } from "./chunk-EYML7T7A.js";
58
+ } from "./chunk-WTY7GTRT.js";
59
59
  import {
60
60
  getPermissionMode,
61
61
  getRunMode,
package/dist/slack.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /** `usage` コマンドが利用状況をポーリングする間隔 */
2
2
  export declare const USAGE_POLL_INTERVAL_SECONDS = 600;
3
+ /** 取得結果を使い回す期間。この間は usage API を叩かない */
4
+ export declare const USAGE_CACHE_TTL_SECONDS = 600;
3
5
  export declare function send(payload: Record<string, unknown>): Promise<void>;
4
6
  export interface UsageInfo {
5
7
  fiveHourUtilization: number | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.142.2",
3
+ "version": "0.142.4",
4
4
  "description": "CLI tool that polls GitHub Issues/PRs and delegates work to Claude CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",