fix(memory-ingest): configurable import timeout + resume-on-timeout messaging (#1611)

The gbrain import (the long pole on big brains) had a hardcoded 30-min timeout,
so large memory corpora got SIGTERM'd mid-import on /sync-gbrain --full. Make it
configurable via GSTACK_INGEST_TIMEOUT_MS (default 30 min, validated 1min–24h).

gstack can't drive gbrain's internal resume, but the existing SIGTERM forwarder
already preserves gbrain's import-checkpoint.json, so the next run resumes. On a
timeout we now say so explicitly ('checkpoint preserved — re-run /sync-gbrain to
resume, raise GSTACK_INGEST_TIMEOUT_MS for big brains') instead of surfacing a
bare 'exited null'. True gstack-driven ingest-resume is deferred to gbrain
(.context/gbrain-asks.md).

Also guards the module's main() behind import.meta.main so resolveImportTimeoutMs
is unit-testable; the orchestrator runs it as a subprocess where main still fires.
New test/memory-ingest-timeout.test.ts pins default/override/invalid resolution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-30 10:50:46 -07:00
parent 627c68ef39
commit c54ece27a4
2 changed files with 82 additions and 6 deletions
+27
View File
@@ -0,0 +1,27 @@
import { describe, test, expect } from "bun:test";
import { resolveImportTimeoutMs } from "../bin/gstack-memory-ingest";
// #1611: the gbrain import timeout is configurable via GSTACK_INGEST_TIMEOUT_MS
// (default 30 min) so big-brain --full ingests aren't SIGTERM'd mid-import.
const DEFAULT = 30 * 60 * 1000;
describe("resolveImportTimeoutMs", () => {
test("unset → 30 min default", () => {
expect(resolveImportTimeoutMs(undefined)).toBe(DEFAULT);
expect(resolveImportTimeoutMs("")).toBe(DEFAULT);
});
test("valid override is honored", () => {
expect(resolveImportTimeoutMs("3600000")).toBe(3_600_000); // 1h
expect(resolveImportTimeoutMs("60000")).toBe(60_000); // floor
expect(resolveImportTimeoutMs("86400000")).toBe(86_400_000); // ceiling
});
test("invalid / out-of-range → default (no SIGTERM-too-soon footgun)", () => {
expect(resolveImportTimeoutMs("nope")).toBe(DEFAULT);
expect(resolveImportTimeoutMs("0")).toBe(DEFAULT);
expect(resolveImportTimeoutMs("59999")).toBe(DEFAULT); // below 1min floor
expect(resolveImportTimeoutMs("86400001")).toBe(DEFAULT); // above 24h ceiling
expect(resolveImportTimeoutMs("-5")).toBe(DEFAULT);
});
});