Skip to content

Commit 7f2b3c9

Browse files
NagyViktNagyViktOmX
authored
Clarify sandbox GitHub auth failures (#540)
gh auth status can report an invalid hosts.yml token when the real problem is blocked GitHub API connectivity in Codex or another sandbox. The release command and review bot now probe gh api user before asking for re-auth, continue when that probe succeeds, and report network/sandbox failure when the API is unreachable. Constraint: Old gh auth status output can conflate API connectivity failure with invalid stored credentials Rejected: Always tell users to run gh auth login | repeats a false fix when the token is valid but api.github.com is unreachable Confidence: high Scope-risk: narrow Directive: Do not collapse GitHub auth failures back to one re-auth message without preserving API-connectivity diagnostics Tested: bash -n scripts/review-bot-watch.sh Tested: bash -n templates/scripts/review-bot-watch.sh Tested: node --test test/release.test.js Tested: node --test test/agents.test.js Co-authored-by: NagyVikt <nagy.viktordp@gmail.com> Co-authored-by: OmX <omx@oh-my-codex.dev>
1 parent 32da724 commit 7f2b3c9

5 files changed

Lines changed: 205 additions & 9 deletions

File tree

scripts/review-bot-watch.sh

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,36 @@ normalize_bool() {
6464
esac
6565
}
6666

67+
gh_api_probe_looks_like_network_failure() {
68+
local probe_output="$1"
69+
[[ "$probe_output" =~ error[[:space:]]connecting[[:space:]]to[[:space:]]api\.github\.com|Could[[:space:]]not[[:space:]]resolve[[:space:]]host|could[[:space:]]not[[:space:]]resolve[[:space:]]host|Failed[[:space:]]to[[:space:]]connect|failed[[:space:]]to[[:space:]]connect|Network[[:space:]]is[[:space:]]unreachable|network[[:space:]]is[[:space:]]unreachable|Connection[[:space:]]timed[[:space:]]out|connection[[:space:]]timed[[:space:]]out|Temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution|temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution ]]
70+
}
71+
72+
ensure_gh_auth_or_explain() {
73+
local auth_output probe_output
74+
if auth_output="$(gh auth status 2>&1)"; then
75+
return 0
76+
fi
77+
78+
if probe_output="$(gh api user --jq .login 2>&1)"; then
79+
echo "[review-bot-watch] gh auth status failed, but gh api user succeeded; continuing with usable GitHub auth." >&2
80+
return 0
81+
fi
82+
83+
if gh_api_probe_looks_like_network_failure "$probe_output"; then
84+
echo "[review-bot-watch] GitHub API is unreachable, so gh cannot validate the stored token." >&2
85+
echo "[review-bot-watch] This is a network or Codex sandbox connectivity problem, not proof that the token is invalid." >&2
86+
echo "$probe_output" >&2
87+
return 1
88+
fi
89+
90+
echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
91+
if [[ -n "$auth_output" ]]; then
92+
echo "$auth_output" >&2
93+
fi
94+
return 1
95+
}
96+
6797
ONCE=0
6898

6999
while [[ $# -gt 0 ]]; do
@@ -153,8 +183,7 @@ if ! command -v codex >/dev/null 2>&1; then
153183
exit 127
154184
fi
155185

156-
if ! gh auth status >/dev/null 2>&1; then
157-
echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
186+
if ! ensure_gh_auth_or_explain; then
158187
exit 1
159188
fi
160189

src/cli/main.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3326,6 +3326,25 @@ function renderGeneratedReleaseNotes(entries, currentTag, previousTag) {
33263326
return `GitGuardex ${currentTag}\n\n${intro}\n\n${sections}`;
33273327
}
33283328

3329+
function describeGhAuthFailure(ghBin, authStatus) {
3330+
if (authStatus.error) {
3331+
return `unable to run '${ghBin} auth status': ${authStatus.error.message}`;
3332+
}
3333+
3334+
const authDetails = (authStatus.stderr || authStatus.stdout || '').trim();
3335+
const apiProbe = run(ghBin, ['api', 'user', '--jq', '.login'], { timeout: 20_000 });
3336+
if (apiProbe.status === 0) {
3337+
return '';
3338+
}
3339+
3340+
const apiDetails = (apiProbe.stderr || apiProbe.stdout || apiProbe.error?.message || '').trim();
3341+
if (/error connecting to api\.github\.com|could not resolve host|failed to connect|network is unreachable|connection timed out|temporary failure in name resolution/i.test(apiDetails)) {
3342+
return `GitHub API is unreachable, so '${ghBin} auth status' cannot validate the stored token. This is a network or sandbox connectivity problem, not proof that the token is invalid.${apiDetails ? `\n${apiDetails}` : ''}`;
3343+
}
3344+
3345+
return `'${ghBin}' auth is unavailable.${authDetails ? `\n${authDetails}` : ''}`;
3346+
}
3347+
33293348
function buildReleaseNotesFromReadme(repoRoot, currentTag, previousTag) {
33303349
const readme = readRepoReadme(repoRoot);
33313350
const entries = parseReadmeReleaseEntries(readme);
@@ -3353,12 +3372,11 @@ function release(rawArgs) {
33533372
}
33543373

33553374
const ghAuthStatus = run(GH_BIN, ['auth', 'status'], { timeout: 20_000 });
3356-
if (ghAuthStatus.error) {
3357-
throw new Error(`Release blocked: unable to run '${GH_BIN} auth status': ${ghAuthStatus.error.message}`);
3358-
}
33593375
if (ghAuthStatus.status !== 0) {
3360-
const details = (ghAuthStatus.stderr || ghAuthStatus.stdout || '').trim();
3361-
throw new Error(`Release blocked: '${GH_BIN}' auth is unavailable.${details ? `\n${details}` : ''}`);
3376+
const ghAuthFailure = describeGhAuthFailure(GH_BIN, ghAuthStatus);
3377+
if (ghAuthFailure) {
3378+
throw new Error(`Release blocked: ${ghAuthFailure}`);
3379+
}
33623380
}
33633381

33643382
const releasePackageJson = readReleaseRepoPackageJson(repoRoot);

templates/scripts/review-bot-watch.sh

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,36 @@ normalize_bool() {
6464
esac
6565
}
6666

67+
gh_api_probe_looks_like_network_failure() {
68+
local probe_output="$1"
69+
[[ "$probe_output" =~ error[[:space:]]connecting[[:space:]]to[[:space:]]api\.github\.com|Could[[:space:]]not[[:space:]]resolve[[:space:]]host|could[[:space:]]not[[:space:]]resolve[[:space:]]host|Failed[[:space:]]to[[:space:]]connect|failed[[:space:]]to[[:space:]]connect|Network[[:space:]]is[[:space:]]unreachable|network[[:space:]]is[[:space:]]unreachable|Connection[[:space:]]timed[[:space:]]out|connection[[:space:]]timed[[:space:]]out|Temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution|temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution ]]
70+
}
71+
72+
ensure_gh_auth_or_explain() {
73+
local auth_output probe_output
74+
if auth_output="$(gh auth status 2>&1)"; then
75+
return 0
76+
fi
77+
78+
if probe_output="$(gh api user --jq .login 2>&1)"; then
79+
echo "[review-bot-watch] gh auth status failed, but gh api user succeeded; continuing with usable GitHub auth." >&2
80+
return 0
81+
fi
82+
83+
if gh_api_probe_looks_like_network_failure "$probe_output"; then
84+
echo "[review-bot-watch] GitHub API is unreachable, so gh cannot validate the stored token." >&2
85+
echo "[review-bot-watch] This is a network or Codex sandbox connectivity problem, not proof that the token is invalid." >&2
86+
echo "$probe_output" >&2
87+
return 1
88+
fi
89+
90+
echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
91+
if [[ -n "$auth_output" ]]; then
92+
echo "$auth_output" >&2
93+
fi
94+
return 1
95+
}
96+
6797
ONCE=0
6898

6999
while [[ $# -gt 0 ]]; do
@@ -153,8 +183,7 @@ if ! command -v codex >/dev/null 2>&1; then
153183
exit 127
154184
fi
155185

156-
if ! gh auth status >/dev/null 2>&1; then
157-
echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
186+
if ! ensure_gh_auth_or_explain; then
158187
exit 1
159188
fi
160189

test/agents.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,36 @@ test('review-bot-watch uses explicit codex-agent flags for argument parsing comp
8484
});
8585

8686

87+
test('review-bot-watch reports GitHub network failure separately from invalid auth', () => {
88+
const repoDir = initRepo();
89+
seedCommit(repoDir);
90+
const fakeGh = createFakeGhScript(`
91+
if [[ "$1" == "auth" && "$2" == "status" ]]; then
92+
echo "github.com" >&2
93+
echo " X github.com: authentication failed" >&2
94+
echo " - The github.com token in /home/deadpool/.config/gh/hosts.yml is no longer valid." >&2
95+
exit 1
96+
fi
97+
if [[ "$1" == "api" && "$2" == "user" ]]; then
98+
echo "error connecting to api.github.com" >&2
99+
exit 1
100+
fi
101+
echo "unexpected gh args: $*" >&2
102+
exit 1
103+
`);
104+
const fakeCodex = createFakeBin('codex', 'exit 0');
105+
106+
const result = runReviewBot(['--once'], repoDir, {
107+
PATH: `${fakeGh.fakeBin}:${fakeCodex.fakeBin}:${process.env.PATH}`,
108+
});
109+
110+
assert.equal(result.status, 1);
111+
assert.match(result.stderr, /GitHub API is unreachable/);
112+
assert.match(result.stderr, /network or Codex sandbox connectivity problem/);
113+
assert.doesNotMatch(result.stderr, /Run: gh auth login/);
114+
});
115+
116+
87117
test('review command launches local review-bot script and accepts legacy start token', () => {
88118
const repoDir = initRepo();
89119
const scriptsDir = path.join(repoDir, 'scripts');

test/release.test.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,96 @@ exit 1
257257
});
258258

259259

260+
test('release reports GitHub network failure separately from invalid auth', () => {
261+
const repoDir = initRepoOnBranch('main');
262+
seedReleasePackageManifest(repoDir);
263+
fs.writeFileSync(
264+
path.join(repoDir, 'README.md'),
265+
`## Release notes
266+
267+
### v${cliVersion}
268+
- Current release fix.
269+
`,
270+
'utf8',
271+
);
272+
seedCommit(repoDir);
273+
274+
const fakeGh = createFakeGhScript(`
275+
if [[ "$1" == "auth" && "$2" == "status" ]]; then
276+
echo "github.com" >&2
277+
echo " X github.com: authentication failed" >&2
278+
echo " - The github.com token in /home/deadpool/.config/gh/hosts.yml is no longer valid." >&2
279+
exit 1
280+
fi
281+
if [[ "$1" == "api" && "$2" == "user" ]]; then
282+
echo "error connecting to api.github.com" >&2
283+
exit 1
284+
fi
285+
echo "unexpected gh args: $*" >&2
286+
exit 1
287+
`);
288+
289+
const result = runNodeWithEnv(['release'], repoDir, {
290+
GUARDEX_RELEASE_REPO: repoDir,
291+
GUARDEX_GH_BIN: fakeGh.fakePath,
292+
});
293+
294+
assert.equal(result.status, 1);
295+
assert.match(result.stderr, /GitHub API is unreachable/);
296+
assert.match(result.stderr, /network or sandbox connectivity problem/);
297+
assert.doesNotMatch(result.stderr, /Run: gh auth login/);
298+
});
299+
300+
301+
test('release continues when gh api proves auth despite auth status failure', () => {
302+
const repoDir = initRepoOnBranch('main');
303+
seedReleasePackageManifest(repoDir);
304+
fs.writeFileSync(
305+
path.join(repoDir, 'README.md'),
306+
`## Release notes
307+
308+
### v${cliVersion}
309+
- Current release fix.
310+
`,
311+
'utf8',
312+
);
313+
seedCommit(repoDir);
314+
315+
const markerPath = path.join(repoDir, '.gh-release-auth-fallback-called');
316+
const fakeGh = createFakeGhScript(`
317+
if [[ "$1" == "auth" && "$2" == "status" ]]; then
318+
echo "github.com auth status failed" >&2
319+
exit 1
320+
fi
321+
if [[ "$1" == "api" && "$2" == "user" ]]; then
322+
echo "NagyVikt"
323+
exit 0
324+
fi
325+
if [[ "$1" == "release" && "$2" == "list" ]]; then
326+
exit 0
327+
fi
328+
if [[ "$1" == "release" && "$2" == "view" ]]; then
329+
exit 1
330+
fi
331+
if [[ "$1" == "release" && "$2" == "create" ]]; then
332+
printf '%s\\n' "$@" > "${markerPath}"
333+
printf '%s\\n' "https://example.test/releases/tag/v${cliVersion}"
334+
exit 0
335+
fi
336+
echo "unexpected gh args: $*" >&2
337+
exit 1
338+
`);
339+
340+
const result = runNodeWithEnv(['release'], repoDir, {
341+
GUARDEX_RELEASE_REPO: repoDir,
342+
GUARDEX_GH_BIN: fakeGh.fakePath,
343+
});
344+
345+
assert.equal(result.status, 0, result.stderr || result.stdout);
346+
assert.match(fs.readFileSync(markerPath, 'utf8'), /^create$/m);
347+
});
348+
349+
260350
test('typo helper maps relaese/realaese to release', () => {
261351
const repoDir = initRepoOnBranch('main');
262352
seedReleasePackageManifest(repoDir);

0 commit comments

Comments
 (0)