From 726967855f9467f33efdcad791532d7128ed5f8d Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:44:54 -0700 Subject: [PATCH] feat(spend): the spend alarm names the user Cofounder's condition on the Pro run ceiling (2026-10-15): the $2-a-day alarm names the user, so whoever is paged sees who without a lookup. Each alarm line now carries the username next to the userId, looked up only for the users who crossed the line; a failed lookup still logs the id. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015NDyNbwmCco62PAAviSL1k --- .../unit/services/spendAlarmService.test.js | 12 ++++++++++++ backend/services/spendAlarmService.ts | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/backend/__tests__/unit/services/spendAlarmService.test.js b/backend/__tests__/unit/services/spendAlarmService.test.js index 86e9444954..666bf6440c 100644 --- a/backend/__tests__/unit/services/spendAlarmService.test.js +++ b/backend/__tests__/unit/services/spendAlarmService.test.js @@ -102,6 +102,18 @@ describe('checkSpend', () => { }); }); + test('names the user in the line, so whoever is paged sees who without a lookup', async () => { + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const User = require('../../../models/User'); + await User.deleteMany({}); + await User.create({ _id: USER_A, username: 'maya', email: 'maya@example.com', password: 'x'.repeat(12) }); + await AgentRun.create(run(USER_A, [{ model: 'deepseek-v4-flash', promptTokens: 2_000_000, completionTokens: 2_000_000 }])); + + await spend.checkSpend(NOW); + + expect(alarmLines(warn)).toEqual([expect.objectContaining({ userId: String(USER_A), username: 'maya' })]); + }); + test('SPEND_ALARM_USD_PER_DAY moves the line, and a bad value falls back to $2', async () => { const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); await AgentRun.create(run(USER_B, [{ model: 'deepseek-v4-flash', promptTokens: 1_000_000, completionTokens: 1_000_000 }])); diff --git a/backend/services/spendAlarmService.ts b/backend/services/spendAlarmService.ts index ff51dfe13a..fcc18a71af 100644 --- a/backend/services/spendAlarmService.ts +++ b/backend/services/spendAlarmService.ts @@ -127,12 +127,29 @@ export const checkSpend = async (now: Date = new Date()): Promise = const day = utcDayStart(now).toISOString().slice(0, 10); const over = (await userSpendForDay(now)).filter((spend) => spend.usd >= threshold); const fresh = over.filter((spend) => !alarmed.has(`${day}:${spend.userId}`)); + // Name each user in the line, so whoever is paged sees who without a lookup + // (Cofounder, 2026-10-15). A failed lookup still logs the id. + const usernames = new Map(); + if (fresh.length) { + try { + // eslint-disable-next-line global-require, @typescript-eslint/no-require-imports + const User = require('../models/User'); + const ids = fresh.map((spend) => spend.userId).filter((id) => /^[a-f\d]{24}$/i.test(id)); + const users = await User.find({ _id: { $in: ids } }).select('username').lean(); + users.forEach((user: { _id: unknown; username?: string }) => { + if (user.username) usernames.set(String(user._id), user.username); + }); + } catch (error) { + console.warn('[spend-alarm] username lookup failed; logging ids only:', (error as Error).message); + } + } for (const spend of fresh) { alarmed.add(`${day}:${spend.userId}`); console.warn(JSON.stringify({ alarm: 'user_spend', day, userId: spend.userId, + ...(usernames.has(spend.userId) ? { username: usernames.get(spend.userId) } : {}), usd: Number(spend.usd.toFixed(4)), thresholdUsd: threshold, runs: spend.runs,