@link-assistant/hive-mind 2.11.4 → 2.11.6

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.
@@ -14,6 +14,7 @@ const use = globalThis.use;
14
14
 
15
15
  // Use command-stream for consistent $ behavior; wrap with rate-limit retry (#1726)
16
16
  const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
17
+ const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
17
18
  const $ = wrapDollarWithGhRetry((await use('command-stream')).$);
18
19
 
19
20
  // Import shared library functions
@@ -179,7 +180,7 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
179
180
  // branch. Attempting the push there is guaranteed to be rejected
180
181
  // with "permission denied" and is unnecessary, so we skip it and
181
182
  // keep working on the PR branch.
182
- const currentUserResult = await $`gh api user --jq .login`;
183
+ const currentUserResult = await $(QUIET_PROBE)`gh api user --jq .login`;
183
184
  const currentUser = currentUserResult.code === 0 ? currentUserResult.stdout.toString().trim() : null;
184
185
  const pushDecision = shouldPushDefaultBranchToFork({ currentUser, forkedRepo });
185
186
 
@@ -6,6 +6,7 @@
6
6
  // Issue #1625: centralized markers + tracked posting for the "empty repo"
7
7
  // issue comment so it's excluded from --auto-attach-solution-summary's check.
8
8
  import { REPOSITORY_INITIALIZATION_REQUIRED_MARKER, postTrackedComment } from './tool-comments.lib.mjs';
9
+ import { QUIET_PROBE } from './quiet-probe.lib.mjs'; // issue #2130: keep read-only probe payloads out of the attached log
9
10
 
10
11
  export async function setupRepositoryAndClone({ argv, owner, repo, forkOwner, forkRepoName, tempDir, isContinueMode, issueUrl, log, $, needsClone = true }) {
11
12
  // Set up repository and handle forking
@@ -28,14 +29,49 @@ export async function setupRepositoryAndClone({ argv, owner, repo, forkOwner, fo
28
29
  const prForkRemote = await setupPrForkRemote(tempDir, argv, prForkOwner, repo, isContinueMode, owner);
29
30
 
30
31
  // Set up git authentication using gh
31
- const authSetupResult = await $({ cwd: tempDir })`gh auth setup-git 2>&1`;
32
- if (authSetupResult.code !== 0) {
33
- await log('Note: gh auth setup-git had issues, continuing anyway\n');
34
- }
32
+ await setupGitCredentialHelper({ tempDir, log, $ });
35
33
 
36
34
  return { repoToClone, forkedRepo, upstreamRemote, prForkRemote, prForkOwner };
37
35
  }
38
36
 
37
+ /**
38
+ * Point git at `gh` for GitHub credentials.
39
+ *
40
+ * Issue #2130: `gh auth setup-git` only ever writes the *global* gitconfig. In
41
+ * a container where `~/.gitconfig` is bind-mounted, git cannot replace it and
42
+ * gh reports `failed to set up git credential helper: failed to run git: error:
43
+ * could not write config file /home/box/.gitconfig: Device or resource busy`.
44
+ * Hive Mind used to mirror that line raw and continue with no credential helper
45
+ * at all, so the real failure only surfaced later as a push error.
46
+ *
47
+ * The clone-local config is equivalent for our purposes and is never
48
+ * bind-mounted, so it is used as the fallback. The empty `helper` entry first
49
+ * clears any inherited helper, exactly as `gh auth setup-git` does.
50
+ */
51
+ export async function setupGitCredentialHelper({ tempDir, log, $, hosts = ['github.com'] }) {
52
+ const authSetupResult = await $({ cwd: tempDir, ...QUIET_PROBE })`gh auth setup-git 2>&1`;
53
+ if (authSetupResult.code === 0) return { scope: 'global', hosts };
54
+
55
+ const reason = (authSetupResult.stdout?.toString() || authSetupResult.stderr?.toString() || '').trim();
56
+ await log(`ℹ️ gh auth setup-git could not write the global gitconfig${reason ? `: ${reason.split('\n')[0]}` : ''}`, { verbose: true });
57
+
58
+ const failures = [];
59
+ for (const host of hosts) {
60
+ const key = `credential.https://${host}.helper`;
61
+ const cleared = await $({ cwd: tempDir, ...QUIET_PROBE })`git config --local --replace-all ${key} ""`;
62
+ const configured = await $({ cwd: tempDir, ...QUIET_PROBE })`git config --local --add ${key} ${'!gh auth git-credential'}`;
63
+ if (cleared.code !== 0 || configured.code !== 0) failures.push(host);
64
+ }
65
+
66
+ if (failures.length) {
67
+ await log(`⚠️ Could not configure a git credential helper for ${failures.join(', ')} - pushes may require credentials in the remote URL`, { level: 'warning' });
68
+ return { scope: 'none', hosts: failures };
69
+ }
70
+
71
+ await log('🔑 Configured the gh credential helper for this clone (the global gitconfig is not writable)', { verbose: true });
72
+ return { scope: 'local', hosts };
73
+ }
74
+
39
75
  async function setupRepository(argv, owner, repo, forkOwner, issueUrl, forkRepoName) {
40
76
  const repository = await import('./solve.repository.lib.mjs');
41
77
  const { setupRepository: setupRepoFn } = repository;
@@ -14,6 +14,7 @@ const use = globalThis.use;
14
14
 
15
15
  // Use command-stream for consistent $ behavior; wrap with rate-limit retry (#1726)
16
16
  const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
17
+ const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
17
18
  const $ = wrapDollarWithGhRetry((await use('command-stream')).$);
18
19
  const os = (await use('os')).default;
19
20
  const path = (await use('path')).default;
@@ -57,7 +58,7 @@ export const getRootRepository = async (owner, repo) => {
57
58
  // Check if current user has a fork of the given root repository
58
59
  export const checkExistingForkOfRoot = async rootRepo => {
59
60
  try {
60
- const userResult = await lib.ghCmdRetry(() => $`gh api user --jq .login`, { label: 'get user (fork check)' });
61
+ const userResult = await lib.ghCmdRetry(() => $(QUIET_PROBE)`gh api user --jq .login`, { label: 'get user (fork check)' });
61
62
  if (userResult.code !== 0) return null;
62
63
  const currentUser = userResult.stdout.toString().trim();
63
64
  // Issue #2119: build the jq expression in JS. Its double quotes belong to jq,
@@ -378,7 +379,7 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
378
379
  await log(`${formatAligned('', 'Checking fork status...', '')}\n`);
379
380
 
380
381
  // Get current user (issue #1536: retry on transient network errors)
381
- const userResult = await lib.ghCmdRetry(() => $`gh api user --jq .login`, { label: 'get current user' });
382
+ const userResult = await lib.ghCmdRetry(() => $(QUIET_PROBE)`gh api user --jq .login`, { label: 'get current user' });
382
383
  if (userResult.code !== 0) {
383
384
  await log(`${formatAligned('❌', 'Error:', 'Failed to get current user')}`);
384
385
  await safeExit(1, 'Repository setup failed');
@@ -1191,7 +1192,7 @@ export const setupPrForkRemote = async (tempDir, argv, prForkOwner, repo, isCont
1191
1192
 
1192
1193
  // Get current user to check if it's someone else's fork
1193
1194
  await log(`\n${formatAligned('🔍', 'Checking PR fork:', 'Determining if branch is in another fork...')}`);
1194
- const userResult = await $`gh api user --jq .login`;
1195
+ const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
1195
1196
  if (userResult.code !== 0) {
1196
1197
  await log(`${formatAligned('⚠️', 'Warning:', 'Failed to get current user, cannot set up pr-fork remote')}`);
1197
1198
  return null;
@@ -15,6 +15,7 @@ const use = globalThis.use;
15
15
  // Use command-stream for consistent $ behavior across runtimes
16
16
  const { $: __rawDollar$ } = await use('command-stream');
17
17
  const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
18
+ const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
18
19
  const $ = wrapDollarWithGhRetry(__rawDollar$);
19
20
  const path = (await use('path')).default;
20
21
 
@@ -719,7 +720,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
719
720
 
720
721
  try {
721
722
  // Get the current user's GitHub username
722
- const userResult = await $`gh api user --jq .login`;
723
+ const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
723
724
 
724
725
  if (userResult.code !== 0) {
725
726
  throw new Error(`Failed to get current user: ${userResult.stderr ? userResult.stderr.toString() : 'Unknown error'}`);
@@ -1139,7 +1140,7 @@ export const { TOOL_GENERATED_COMMENT_MARKERS, isToolGeneratedComment, trackTool
1139
1140
  export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, prNumber, issueNumber) => {
1140
1141
  try {
1141
1142
  // Get the current user's GitHub username
1142
- const userResult = await $`gh api user --jq .login`;
1143
+ const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
1143
1144
  if (userResult.code !== 0) {
1144
1145
  return false; // Cannot determine, default to not attaching
1145
1146
  }
@@ -308,13 +308,20 @@ export const performSystemChecks = async (minDiskSpace = 10240, skipToolConnecti
308
308
  const { validateFormalAiToolConnection } = await import('./formal-ai.lib.mjs');
309
309
  const formalAiValidation = await validateFormalAiToolConnection(argv.tool || 'claude');
310
310
  isToolConnected = formalAiValidation.valid;
311
+ // Record the wrapper version on both paths: the wrapper builds the tool's
312
+ // argv, so its version is the first thing needed to explain a failure
313
+ // (issue #2130's logs recorded neither, which left the round-2 claude
314
+ // failure unattributable).
315
+ const formalAiVersionLine = `📦 Formal AI wrapper version: ${formalAiValidation.formalAiVersion || 'unknown'}`;
311
316
  if (isToolConnected) {
312
317
  await log(`✅ Formal AI wrapper and ${argv.tool || 'claude'} CLI are available`);
318
+ await log(formalAiVersionLine);
313
319
  if (formalAiValidation.version) {
314
320
  await log(`📦 ${argv.tool || 'claude'} CLI version: ${formalAiValidation.version}`);
315
321
  }
316
322
  } else {
317
323
  await log(`❌ Formal AI dispatch validation failed: ${formalAiValidation.error}`, { level: 'error' });
324
+ await log(` ${formalAiVersionLine}`, { level: 'error' });
318
325
  await log(' Install or update the wrapper with: cargo install formal-ai', { level: 'error' });
319
326
  return false;
320
327
  }
@@ -24,6 +24,7 @@ import { reportError } from './sentry.lib.mjs';
24
24
  export { createCredentialStreamSanitizer };
25
25
 
26
26
  import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
27
+ import { QUIET_PROBE } from './quiet-probe.lib.mjs'; // issue #2130: never mirror the call that discovers which secrets must be masked
27
28
  // Dynamic imports for runtime dependencies
28
29
  const getOsModule = async () => (await import('os')).default;
29
30
  const getPathModule = async () => (await import('path')).default;
@@ -255,8 +256,12 @@ export const getGitHubTokensFromCommand = async () => {
255
256
  const tokens = [];
256
257
 
257
258
  try {
258
- // Run gh auth status to get token info
259
- const authResult = await $`gh auth status 2>&1`.catch(() => ({ stdout: '', stderr: '' }));
259
+ // Run gh auth status to get token info.
260
+ // Issue #2130: never mirror this. The whole point of the call is to learn
261
+ // which secrets have to be masked, so its output is the one thing that is
262
+ // guaranteed to be unmasked at this moment - and it was being echoed into
263
+ // the log that later gets attached to the pull request.
264
+ const authResult = await $(QUIET_PROBE)`gh auth status 2>&1`.catch(() => ({ stdout: '', stderr: '' }));
260
265
  const authOutput = authResult.stdout?.toString() + authResult.stderr?.toString() || '';
261
266
 
262
267
  // Look for token patterns in the output