Skip to content

The "New subscribers" notification toggle controls nothing and its Slack ping is unmutable — the subscribe path calls createNotification directly, bypassing notifyUser #656

Description

@serge-ivo

The "New subscribers" notification toggle controls nothing, and its Slack ping cannot be muted — one of six notification types bypasses notifyUser

Found in the D1 sweep prompted by #438. Two writers of notifications rows with different behaviour, and the type the console offers a control for is on the wrong one.

Verified — the subscribe path calls createNotification directly

workers/api/src/routes/instances.ts:264-269:

		await createNotification(
			c.env.DB, creator.owner_id, "subscribe",
			`New subscriber: ${subscriber?.github_login || "someone"}`,
			`${subscriber?.github_login || "A user"} subscribed to ${agent.name}.`,
			agent.id,
		);
grep -rn "createNotification(" workers/api/src --include="*.ts" | grep -v test → 3
  (the definition at routes/notifications.ts:65, routes/push.ts:273, routes/instances.ts:264)
grep -rn 'notifyUser(…, "subscribe"' workers/api/src --include="*.ts" | grep -v test → 0

notifyUser (routes/push.ts:234-278) is the funnel that consults the preference:

		interrupt = pushAllowedByPreference(parseAccountPreferences(row?.preferences).notifications, type, kind);

grep -rn "pushAllowedByPreference" workers/api/src --include="*.ts" | grep -v test → 3 hits: the definition (lib/notifications.ts:172), the import and the single call in routes/push.ts:251. Nothing else reads the preference.

Verified — the type IS offered as a control

workers/api/src/lib/notifications.ts:76-81:

	{
		id: "subscribe",
		label: "New subscribers",
		description: "Someone subscribing to an agent you publish.",
		alerts: false,
	},

served at routes/preferences.ts:51 (notificationTypes: NOTIFICATION_TYPES) and rendered as a toggle by store/console/src/components/NotificationPreferences.tsx, mounted at store/console/src/pages/Preferences.tsx:162-166.

The other five declared types (apply, coding, deploy, loop, trigger) all go through notifyUser — 13 call sites.

Verified — what the bypass costs

workers/api/src/routes/notifications.ts:75const interrupt = opts.interrupt !== false;. The subscribe call passes no opts, so interrupt is true, and :95-105 then fires the Slack webhook:

	// Slack is an interruption channel like the push is, so a suppressed copy is silent there
	// too. Muting deploys in the console and still being pinged in Slack would make the control
	// a half-truth.
	if (!interrupt) return;

It also passes no dedupeKey, so the #361 duplicate floor (routes/push.ts:255-270) has no key to match on, and it never calls sendPushToUser.

User-visible impact

A creator who turns off "New subscribers" keeps getting Slack pings on every subscription, and the toggle gives no feedback that it did nothing — the card's own copy promises the opposite. The mute is vacuously honoured for web push only because no push is attempted for this type at all, which is a second, quieter surprise: the toggle is advertised for a channel that never fires.

Not claimed

Not reproduced against a live Slack webhook. The three code paths above settle it.

Refs #438, #361.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2: correctnessReal defect, no live harm today — inert fields, miscounts, missing guardsbackendBackend / Worker / API workbugSomething isn't workingobservabilityA displayed value the code cannot produce, or that means something other than its label

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions