mirror of https://github.com/garrytan/gstack.git
fix(update-check): crash emits CHECK_FAILED instead of reading as up-to-date
gstack-update-check signals "up to date" with SILENCE, and it runs under set -e — so any unguarded mid-script failure exited quietly and was indistinguishable from a current install. Observed live as a 45-release silent-staleness incident. An ERR trap (with -E so it propagates into functions) now emits a CHECK_FAILED sentinel naming the line and status, and exits 0 so caller `|| true` guards can't eat it. Behavioral tests cover both the crash and the healthy-silent paths; egress-receipt wiring is untouched and still pinned by test/egress-receipt-wiring.test.ts. Fixes #1974. (#2378's HEAD-SHA staleness half was already fixed on main by the ls-remote + SHA-pinned VERSION resolution — close as already-fixed.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5c9994a596
commit
2b96678f8a
|
|
@ -13,6 +13,14 @@
|
|||
# GSTACK_STATE_DIR — override ~/.gstack state directory
|
||||
set -euo pipefail
|
||||
|
||||
# A crash must not read as "up to date" (#1974). With set -e, any unguarded
|
||||
# failure used to exit silently — and silence IS the up-to-date signal, so a
|
||||
# broken check was indistinguishable from a current install (observed live as
|
||||
# a 45-release silent-staleness incident). -E propagates the trap into
|
||||
# functions/subshells; exit 0 keeps callers' `|| true` from eating the line.
|
||||
set -E
|
||||
trap 'rc=$?; echo "CHECK_FAILED gstack-update-check crashed (line $LINENO, rc=$rc) — update status UNKNOWN, not up-to-date"; exit 0' ERR
|
||||
|
||||
GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import { describe, test, expect } from 'bun:test';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
// #1974: gstack-update-check runs under set -e, and its "up to date" signal
|
||||
// is SILENCE — so any unguarded crash used to exit quietly and read as
|
||||
// up-to-date (a real 45-release silent-staleness incident). The ERR trap must
|
||||
// convert a crash into a visible CHECK_FAILED line, and the healthy path must
|
||||
// stay silent.
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const SCRIPT = path.join(ROOT, 'bin', 'gstack-update-check');
|
||||
|
||||
function run(env: Record<string, string>) {
|
||||
return spawnSync('bash', [SCRIPT], {
|
||||
encoding: 'utf8',
|
||||
env: { ...process.env, ...env },
|
||||
timeout: 15000,
|
||||
});
|
||||
}
|
||||
|
||||
describe('gstack-update-check crash sentinel (#1974)', () => {
|
||||
test('a mid-script crash emits CHECK_FAILED instead of silent up-to-date', () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-upd-'));
|
||||
try {
|
||||
// STATE_DIR pointing at a FILE makes the state mkdir fail — an
|
||||
// unguarded failure representative of any mid-script crash.
|
||||
const asFile = path.join(dir, 'statefile');
|
||||
fs.writeFileSync(asFile, '');
|
||||
const r = run({ GSTACK_STATE_DIR: asFile, GSTACK_REMOTE_URL: 'file:///dev/null' });
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout).toContain('CHECK_FAILED');
|
||||
expect(r.stdout).toContain('UNKNOWN');
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('healthy up-to-date path stays silent (no sentinel noise)', () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-upd-'));
|
||||
try {
|
||||
const r = run({ GSTACK_STATE_DIR: path.join(dir, 'state'), GSTACK_REMOTE_URL: 'file:///dev/null' });
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout).not.toContain('CHECK_FAILED');
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue