hillclimb 0.8.3 → 0.8.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.
Files changed (2) hide show
  1. package/dist/main.js +58 -16
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -2349,7 +2349,11 @@ var VOLATILE_KEYS = /* @__PURE__ */ new Set([
2349
2349
  "wall_time_ms"
2350
2350
  ]);
2351
2351
  function sessionsDir() {
2352
- return process.env.HILLCLIMB_CODEX_SESSIONS_DIR ?? path9.join(os3.homedir(), ".codex", "sessions");
2352
+ if (process.env.HILLCLIMB_CODEX_SESSIONS_DIR) {
2353
+ return process.env.HILLCLIMB_CODEX_SESSIONS_DIR;
2354
+ }
2355
+ const codexHome = process.env.CODEX_HOME || path9.join(os3.homedir(), ".codex");
2356
+ return path9.join(codexHome, "sessions");
2353
2357
  }
2354
2358
  function asRecord(value) {
2355
2359
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -2519,6 +2523,10 @@ async function findCodexRolloutPath(sessionId) {
2519
2523
  }
2520
2524
  return void 0;
2521
2525
  }
2526
+ async function isCodexInternalThread(payload, sessionId) {
2527
+ if (!sessionId || payload.transcript_path) return false;
2528
+ return await findCodexRolloutPath(sessionId) === void 0;
2529
+ }
2522
2530
  function commonPrefixLength(a, b) {
2523
2531
  const limit = Math.min(a.length, b.length);
2524
2532
  let count = 0;
@@ -11762,14 +11770,20 @@ function getSourceBaseDir(sourceName) {
11762
11770
  }
11763
11771
  }
11764
11772
  function archivePathFor(file) {
11773
+ return path11.posix.join(file.sourceName, path11.basename(file.absolutePath));
11774
+ }
11775
+ function legacyArchivePathFor(file) {
11765
11776
  const baseDir = getSourceBaseDir(file.sourceName);
11766
11777
  const relativePath = file.absolutePath.startsWith(baseDir) ? path11.relative(baseDir, file.absolutePath) : path11.basename(file.absolutePath);
11767
11778
  return path11.join(file.sourceName, relativePath);
11768
11779
  }
11769
11780
  function addGroupToArchive(archive, group, selectedSources) {
11781
+ const used = /* @__PURE__ */ new Set();
11770
11782
  for (const file of group.files) {
11771
11783
  if (!selectedSources.has(file.sourceName)) continue;
11772
- const archivePath = archivePathFor(file);
11784
+ let archivePath = archivePathFor(file);
11785
+ if (used.has(archivePath)) archivePath = legacyArchivePathFor(file);
11786
+ used.add(archivePath);
11773
11787
  if (file.content) {
11774
11788
  archive.append(file.content, { name: archivePath });
11775
11789
  } else {
@@ -13065,7 +13079,7 @@ async function runUploadInner(payload) {
13065
13079
  "warn",
13066
13080
  `Skipping upload: missing required hook fields (session_id=${!!sessionId}, transcript_path=${!!payload.transcript_path}, cwd=${!!cwd})`
13067
13081
  );
13068
- return false;
13082
+ return "skipped";
13069
13083
  }
13070
13084
  const match = await findProjectForCwd(cwd);
13071
13085
  if (!match) {
@@ -13073,13 +13087,13 @@ async function runUploadInner(payload) {
13073
13087
  "warn",
13074
13088
  `Skipping session ${sessionId}: no hillclimb config for cwd ${cwd}. Run \`npx hillclimb\` in the repo.`
13075
13089
  );
13076
- return false;
13090
+ return "skipped";
13077
13091
  }
13078
13092
  const { repoRoot, config } = match;
13079
13093
  const sourceTool = await resolveSourceTool(payload, repoRoot);
13080
13094
  if (!sourceTool) {
13081
13095
  payload.tool = "unknown";
13082
- return false;
13096
+ return "skipped";
13083
13097
  }
13084
13098
  payload.tool = sourceTool;
13085
13099
  appendLog(
@@ -13093,11 +13107,18 @@ async function runUploadInner(payload) {
13093
13107
  sessionId
13094
13108
  );
13095
13109
  if (!transcriptPath) {
13110
+ if (sourceTool === "codex") {
13111
+ appendLog(
13112
+ "info",
13113
+ `Skipping session ${sessionId}: Codex internal thread (no transcript_path and no rollout under ~/.codex/sessions); not a user session`
13114
+ );
13115
+ return "internal-thread";
13116
+ }
13096
13117
  appendLog(
13097
13118
  "warn",
13098
13119
  `Skipping upload: missing required hook fields (session_id=true, transcript_path=false, cwd=true)`
13099
13120
  );
13100
- return false;
13121
+ return "skipped";
13101
13122
  }
13102
13123
  const transcriptResolved = path15.resolve(transcriptPath);
13103
13124
  try {
@@ -13107,23 +13128,32 @@ async function runUploadInner(payload) {
13107
13128
  "warn",
13108
13129
  `Skipping session ${sessionId}: transcript_path is not a file: ${transcriptResolved}`
13109
13130
  );
13110
- return false;
13131
+ return "skipped";
13111
13132
  }
13112
13133
  } catch (err) {
13134
+ const detail = err instanceof Error ? err.message : String(err);
13135
+ const missing = err?.code === "ENOENT";
13136
+ if (missing && eventKind2 === "sessionEnd") {
13137
+ appendLog(
13138
+ "info",
13139
+ `Skipping session ${sessionId}: session ended without any turns; transcript was never written (${transcriptResolved})`
13140
+ );
13141
+ return "skipped";
13142
+ }
13113
13143
  appendLog(
13114
13144
  "warn",
13115
- `Skipping session ${sessionId}: transcript_path not readable (${transcriptResolved}): ${err instanceof Error ? err.message : String(err)}`
13145
+ `Skipping session ${sessionId}: transcript_path not readable (${transcriptResolved}): ${detail}`
13116
13146
  );
13117
- return false;
13147
+ return "skipped";
13118
13148
  }
13119
13149
  if (!await hasAssistantMessage(transcriptResolved)) {
13120
13150
  appendLog(
13121
13151
  "info",
13122
13152
  `Skipping session ${sessionId}: transcript contains no assistant messages (nothing to upload).`
13123
13153
  );
13124
- return false;
13154
+ return "skipped";
13125
13155
  }
13126
- return await uploadSession({
13156
+ const uploaded = await uploadSession({
13127
13157
  sessionId,
13128
13158
  transcriptPath: transcriptResolved,
13129
13159
  repoRoot,
@@ -13132,6 +13162,7 @@ async function runUploadInner(payload) {
13132
13162
  eventKind: eventKind2,
13133
13163
  recordedAt
13134
13164
  });
13165
+ return uploaded ? "uploaded" : "skipped";
13135
13166
  }
13136
13167
  var AGENT_MAX_UPLOAD_BYTES = 1024 * 1024 * 1024;
13137
13168
  var CLI_VERSION = "0.8.0";
@@ -13351,6 +13382,10 @@ async function uploadSession(args) {
13351
13382
  sourceName: sourceTool,
13352
13383
  absolutePath: transcriptPath
13353
13384
  });
13385
+ const legacyTranscriptArchivePath = legacyArchivePathFor({
13386
+ sourceName: sourceTool,
13387
+ absolutePath: transcriptPath
13388
+ });
13354
13389
  const alreadySubmitted = prior?.submitted ?? false;
13355
13390
  const submitThisUpload = config.autoSubmit && !alreadySubmitted;
13356
13391
  if (mode === "meta-refresh" && prior?.contributionId && prior.epochMeta && cursor) {
@@ -13428,7 +13463,7 @@ async function uploadSession(args) {
13428
13463
  epochMeta2 = {
13429
13464
  transitionKind: "legacy-adopted",
13430
13465
  baseline: "legacy-latest-zip",
13431
- transcriptArchivePath,
13466
+ transcriptArchivePath: legacyTranscriptArchivePath,
13432
13467
  rawByteOffset: baseCursor.rawByteOffset,
13433
13468
  rawPrefixSha256: baseCursor.rawPrefixSha256
13434
13469
  };
@@ -13444,7 +13479,7 @@ async function uploadSession(args) {
13444
13479
  epoch: epoch2,
13445
13480
  transitionKind: "legacy-adopted",
13446
13481
  baseline: "legacy-latest-zip",
13447
- transcriptArchivePath,
13482
+ transcriptArchivePath: legacyTranscriptArchivePath,
13448
13483
  cursor: baseCursor,
13449
13484
  recordedAt,
13450
13485
  lineage: codexLineage
@@ -13885,16 +13920,16 @@ async function runUploadWorker() {
13885
13920
  const toolOverride = process.env[TOOL_ENV_FLAG];
13886
13921
  if (toolOverride) payload.tool = toolOverride;
13887
13922
  appendLog("info", `worker: payload summary: ${summarizePayload(payload)}`);
13888
- let completed = false;
13923
+ let outcome = "skipped";
13889
13924
  try {
13890
- completed = await runUploadInner(payload);
13925
+ outcome = await runUploadInner(payload);
13891
13926
  } catch (err) {
13892
13927
  appendLog(
13893
13928
  "error",
13894
13929
  `worker: unexpected error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
13895
13930
  );
13896
13931
  } finally {
13897
- if (completed) {
13932
+ if (outcome !== "internal-thread") {
13898
13933
  await recordDebugLogCompletion({
13899
13934
  kind: "agent",
13900
13935
  tool: fallbackSourceTool(payload),
@@ -16693,6 +16728,13 @@ async function runGitTracesWorker() {
16693
16728
  );
16694
16729
  return;
16695
16730
  }
16731
+ if (tool === "codex" && await isCodexInternalThread(payload, resolveHookSessionId(payload))) {
16732
+ appendLog(
16733
+ "info",
16734
+ `git-traces: skipping Codex internal thread ${resolveHookSessionId(payload)} on ${event} (no transcript_path and no rollout on disk; not a user session)`
16735
+ );
16736
+ return;
16737
+ }
16696
16738
  try {
16697
16739
  await selfHealHook2(payload, tool);
16698
16740
  switch (eventKind2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hillclimb",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Extract and export AI coding tool logs grouped by repo",
5
5
  "license": "MIT",
6
6
  "type": "module",