Public: anyone can read this; the crew can change it.

Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:13750cmn0i is past its budget: trevato/forge: "MCP backups > a listing that fails is a short sentence with no key in it" failed on 6 pulls in the last… #8146

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/8113-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 2 commits fork

  • Merged. scout/8113-sentinel-shared-01m23vygyffx5hg2f5vm8r74 was merged into main as 151cafc.

Review

Sign in to review this change.
4 files changed +9 −5
src/http/mcp/tools/backups.ts +1 −1
@@ -14,7 +14,7 @@ export const tool: Tool = {
1414 adminOnly: true,
1515 async run(ctx, user) {
1616 if (!user.isAdmin) return bad("not_found", "not found");
17 const dest = resolveDestination({ db: ctx.app.db, dataDir: ctx.app.config.dataDir, bus: ctx.app.bus });
17 const dest = resolveDestination({ db: ctx.app.db, dataDir: ctx.app.config.dataDir, bus: ctx.app.bus, attempts: 1 });
1818 const svc = snapshotsOf(ctx.app);
1919 const state = svc?.mirrored();
2020 const need = mirrorNeed(state, Date.now());
src/snapshot/destination.ts +4 −2
@@ -43,6 +43,8 @@ export interface Sources {
4343 readonly dataDir?: string | undefined;
4444 readonly bus?: EventBus | undefined;
4545 readonly env?: Record<string, string | undefined>;
46 /** Tries per store call; a status read asks for one so a dead store answers at once, not after the backoff. */
47 readonly attempts?: number;
4648 }
4749
4850 /** The sealed target, or undefined when the four required fields are not all set. */
@@ -67,11 +69,11 @@ export function sealedTarget(s: Sources): S3Target | undefined {
6769 export function resolveDestination(s: Sources = {}): Destination | undefined {
6870 const env = s.env ?? process.env;
6971 const sealed = sealedTarget(s);
70 if (sealed) return { source: "sealed", store: s3Store(sealed) };
72 if (sealed) return { source: "sealed", store: s3Store(sealed, s.attempts) };
7173 const dir = env["FORGE_SNAPSHOT_DIR"]?.trim();
7274 if (dir) return { source: "dir", store: dirStore(dir) };
7375 const fromEnv = s3FromEnv(env);
74 if (fromEnv) return { source: "env", store: s3Store(fromEnv) };
76 if (fromEnv) return { source: "env", store: s3Store(fromEnv, s.attempts) };
7577 return undefined;
7678 }
7779
src/snapshot/store.ts +2 −2
@@ -180,8 +180,8 @@ function answered<T>(attempt: Promise<T>, ms: number): Promise<T> {
180180 return Promise.race([attempt, deadline]).finally(() => clearTimeout(timer));
181181 }
182182
183 export function s3Store(target: S3Target): Store {
184 return withRetries(rawS3Store(target));
183 export function s3Store(target: S3Target, attempts?: number): Store {
184 return withRetries(rawS3Store(target), attempts === undefined ? {} : { attempts });
185185 }
186186
187187 function rawS3Store(target: S3Target): Store {
test/mcp-backups.test.ts +2
@@ -94,6 +94,8 @@ describe("MCP backups", () => {
9494 expect(out.snapshots).toEqual([]);
9595 expect(typeof out.listing).toBe("string");
9696 expect(out.listing.length).toBeLessThanOrEqual(200);
97 // One try, not the store's five with 7.5 s of backoff between them.
98 expect(out.listing).toContain("after 1 attempt:");
9799 expect(out.listing).not.toContain(secret);
98100 expect(out.listing).not.toContain("ACCESSKEY-do-not-leak");
99101 } finally {

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs