Skip to content

Commit 3f68380

Browse files
theoephraimclaude
andcommitted
Add debug logging to pushWithToken for CI troubleshooting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 889826d commit 3f68380

1 file changed

Lines changed: 74 additions & 3 deletions

File tree

  • packages/bumpy/src/commands

packages/bumpy/src/commands/ci.ts

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ function pushWithToken(rootDir: string, branch: string, config: BumpyConfig): vo
249249
// Both must be cleared for our custom token to be used.
250250
const extraHeaderKey = `http.${server}/.extraheader`;
251251
const savedHeader = tryRunArgs(['git', 'config', '--local', extraHeaderKey], { cwd: rootDir });
252+
log.dim(` extraheader present: ${!!savedHeader}`);
252253

253254
// Collect includeIf entries that point to credential config files
254255
// git config --get-regexp outputs keys in lowercase, so match accordingly
@@ -264,26 +265,96 @@ function pushWithToken(rootDir: string, branch: string, config: BumpyConfig): vo
264265
}
265266
}
266267
}
268+
log.dim(` includeIf entries found: ${savedIncludeIfs.length}`);
269+
for (const entry of savedIncludeIfs) {
270+
log.dim(` ${entry.key}`);
271+
}
272+
273+
// Dump full git config for debugging
274+
const fullConfig = tryRunArgs(['git', 'config', '--local', '--list'], { cwd: rootDir });
275+
if (fullConfig) {
276+
for (const line of fullConfig.split('\n')) {
277+
if (/includeif|extraheader/i.test(line)) {
278+
log.dim(` [git config] ${line}`);
279+
}
280+
}
281+
}
267282

268283
try {
269284
if (savedHeader) {
270285
runArgs(['git', 'config', '--local', '--unset-all', extraHeaderKey], { cwd: rootDir });
271286
}
272287
for (const entry of savedIncludeIfs) {
288+
log.dim(` Unsetting: ${entry.key}`);
273289
tryRunArgs(['git', 'config', '--local', '--unset', entry.key], { cwd: rootDir });
274290
}
291+
292+
// Verify cleanup
293+
const remainingIncludeIf = tryRunArgs(['git', 'config', '--local', '--get-regexp', '^includeif\\.gitdir:'], {
294+
cwd: rootDir,
295+
});
296+
if (remainingIncludeIf) {
297+
log.warn(` includeIf entries still present after cleanup:\n${remainingIncludeIf}`);
298+
} else {
299+
log.dim(' All includeIf entries cleared');
300+
}
301+
302+
// Set both fetch and push URLs to the authed URL
275303
runArgs(['git', 'remote', 'set-url', 'origin', authedUrl], { cwd: rootDir });
304+
runArgs(['git', 'remote', 'set-url', 'origin', authedUrl, '--push'], { cwd: rootDir });
305+
// Verify URLs (redact token)
306+
const verifyFetch = tryRunArgs(['git', 'remote', 'get-url', 'origin'], { cwd: rootDir });
307+
const verifyPush = tryRunArgs(['git', 'remote', 'get-url', 'origin', '--push'], { cwd: rootDir });
308+
log.dim(` Fetch URL: ${verifyFetch?.replace(token, '***')}`);
309+
log.dim(` Push URL: ${verifyPush?.replace(token, '***')}`);
310+
311+
// Check for url.*.insteadOf rewrites (across all config levels)
312+
const insteadOf = tryRunArgs(['git', 'config', '--get-regexp', '^url\\..*\\.insteadof'], { cwd: rootDir });
313+
if (insteadOf) {
314+
log.dim(` url.*.insteadOf rules:\n${insteadOf}`);
315+
}
316+
317+
// Dump the effective resolved push URL from git's perspective
318+
const resolvedRemote = tryRunArgs(['git', 'ls-remote', '--get-url', 'origin'], { cwd: rootDir });
319+
log.dim(` Resolved remote: ${resolvedRemote?.replace(token, '***')}`);
320+
321+
// Check what credential helpers are active
322+
const credHelper = tryRunArgs(['git', 'config', '--show-origin', '--get-all', 'credential.helper'], {
323+
cwd: rootDir,
324+
});
325+
if (credHelper) {
326+
log.dim(` credential.helper:\n${credHelper}`);
327+
}
328+
329+
// Test token validity via API before pushing
330+
const origGhToken = process.env.GH_TOKEN;
331+
process.env.GH_TOKEN = token;
332+
const tokenCheck = tryRunArgs(
333+
['gh', 'api', 'repos/' + repo, '--jq', '.permissions'],
334+
{ cwd: rootDir },
335+
);
336+
log.dim(` BUMPY_GH_TOKEN permissions: ${tokenCheck}`);
337+
if (origGhToken !== undefined) {
338+
process.env.GH_TOKEN = origGhToken;
339+
} else {
340+
delete process.env.GH_TOKEN;
341+
}
342+
276343
try {
277-
runArgs(['git', 'push', '-u', 'origin', branch, '--force'], { cwd: rootDir });
344+
runArgs(['git', 'push', '-u', 'origin', branch, '--force', '--verbose'], { cwd: rootDir });
278345
} catch (err) {
279346
// Redact token from error messages to prevent leakage in CI logs
280347
const msg = err instanceof Error ? err.message : String(err);
281-
throw new Error(msg.replaceAll(token, '***'));
348+
// Log the redacted stderr for debugging
349+
const redacted = msg.replaceAll(token, '***');
350+
log.error(redacted);
351+
throw new Error(redacted);
282352
}
283353
} finally {
284-
// Restore original URL, extraheader, and includeIf entries
354+
// Restore original URL (both fetch and push), extraheader, and includeIf entries
285355
if (originalUrl) {
286356
runArgs(['git', 'remote', 'set-url', 'origin', originalUrl], { cwd: rootDir });
357+
runArgs(['git', 'remote', 'set-url', 'origin', originalUrl, '--push'], { cwd: rootDir });
287358
}
288359
if (savedHeader) {
289360
runArgs(['git', 'config', '--local', extraHeaderKey, savedHeader], { cwd: rootDir });

0 commit comments

Comments
 (0)