Skip to content

feat(temp-vc): spawn failure handling: hub chat message, last failure per hub, Sentry once per streak - #303

Merged
SyniRon merged 3 commits into
developfrom
feat/290-spawn-failure-handling
Sep 18, 2026
Merged

SyniRon merged 3 commits into
developfrom
feat/290-spawn-failure-handling

Conversation

@SyniRon

@SyniRon SyniRon commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Closes #290. Spec: #285.

What changed

A failed create sends one message in the hub channel's text chat that mentions the member alone. The area is full when Discord reports the category or guild cap. Otherwise the failure has been reported. No DM, no disconnect, and the bot never deletes the message. A create that succeeds and a move-into that fails is one failure: the new channel goes through the empty-channel delete path, and the message goes out only when the runtime's own occupancy map still has the member in the hub.

The runtime keeps each hub's last spawn failure in memory as time and SpawnFailureCause. LastSpawnFailure(hubID) is the reader for the panel. The next successful spawn from that hub clears it. The store writes nothing for a failure. A hub with no category records no category, makes no API call and never reaches Sentry.

Create failures Discord returned capture once per streak per hub: the cap, 403, 429, 5xx, transport. The first failure captures and the next capture waits for a successful spawn from that hub. Delete failures follow the same shape for 403, 5xx and transport. A 429 on delete is a WARN line only. Unknown Channel stays quiet cleanup. commands/temp_vc_errors.go classifies the spawned channel paths on its own. The warden classifier is untouched, so its not-found branch still pages on the warden paths.

TempVCManager grows ChannelMessageSendComplex. The adapter passes WithRetryOnRatelimit(false) like every other call.

Two readings to confirm

  1. A hub with no category sends no message to the member. The spec's lifecycle bullet and the ticket both say "makes no API call". Story 28 reads the other way. Story 31 calls the refusal "a log line and a panel note". If story 28 should win, the change is one call in the no-category branch and one test expectation.
  2. The delete-side streak ends on a successful delete of one of that hub's channels, not on a spawn. The spec says "the same rule as create". With the literal reading, working spawns and broken deletes would page once per leave. The two readings agree in every other case.

For the smoke test

The category cap has no Discord error code of its own. The classifier reads a 400 50035 body for CHANNEL_PARENT_MAX_CHANNELS under parent_id. That shape comes from Discord's documented form-error format, not from a live refusal. Fill a category to 50 on the test guild and check the member gets the "full" text. A failed create on a hub whose text chat the bot cannot post in is the other path worth one join.

Tests

Eight new tests at the seams a seam-sniffer review approved: the fake manager, the captureError seam, store.NewFake() and a pinned clock. They assert the message's channel, allowed mentions and user ID, that the cap and generic texts are two texts under one fixture, the move-into cases, the last failure read back, capture counts over a streak and after a success, and the delete classification table. No test asserts log wording or message copy.

CI gate run locally: lint, tidy, race tests with coverage floors (commands 93.4%), build.

Docs

CLAUDE.md's runtime section and the #278 row of docs/temp-vc-decisions.md record the delete classification and the category cap shape.

…failure per hub, and Sentry once per streak

A failed create sends one message in the hub channel's text chat that
mentions the member alone: the area is full when Discord reports the
category or guild cap, otherwise the failure has been reported. No DM,
no disconnect, and the bot never deletes it. A create that succeeds and
a move-into that fails is the same failure: the new channel goes through
the empty-channel delete path and the message goes out only when the
runtime's own occupancy map still has the member in the hub.

The runtime keeps each hub's last spawn failure in memory as time and
cause, read by LastSpawnFailure for the panel and cleared by the next
successful spawn from that hub. A hub with no category records "no
category", makes no API call and never reaches Sentry.

Create failures Discord returned (the cap, 403, 429, 5xx, transport)
capture once per streak per hub: the first failure captures and the
next capture waits for a successful spawn. Delete failures follow the
same rule for 403, 5xx and transport; a 429 is a WARN line only and
Unknown Channel stays quiet cleanup. temp_vc_errors.go classifies the
spawned channel paths apart from the warden classifier, whose not-found
branch would page on Unknown Channel. The category cap is a 400 50035
whose body carries CHANNEL_PARENT_MAX_CHANNELS, a shape taken from
Discord's docs and not yet seen from a live refusal.

Refs #290
…code review

The once-per-streak check-and-set was written twice, once for creates
and inline for deletes. captureOncePerStreak takes the set. The
classifier's field for 403, 5xx and transport is hardFault, since a 403
is no outage, and the delete path reads capturesOnDelete instead of the
raw field. deleteIfStillEmpty classifies inside the switch, so a nil
error is not classified. RemoveHub no longer clears failure state: the
ticket did not ask for it and a re-registered hub gets a new row ID.

One test added with the seam sniffer's approval: a create refused by
the guild cap leaves the hub's last failure with the full cause.

Refs #290
commands/temp_vc.go: revise "<@%s> your channel could not be created. The failure has been reported." to "<@%s> Cavbot could not create your channel. The error has been reported to S6."
commands/temp_vc.go: revise "<@%s> this area is full. Wait for a channel to empty, then join again." to "<@%s> this category is full. Wait for a channel to empty, then join again."
commands/temp_vc.go: revise SpawnFailureNoCategory "no category" to "hub channel has no category"
commands/temp_vc.go: revise SpawnFailureFull "full" to "this area is full"
commands/temp_vc.go, CLAUDE.md: comments and the CLAUDE.md sentence that quote the two member texts follow the new wording

Review: https://claude.ai/artifact/66Yxk53o26p8YWwY6MSXHV
@SyniRon
SyniRon merged commit f9e5e24 into develop Sep 18, 2026
3 checks passed
@SyniRon
SyniRon deleted the feat/290-spawn-failure-handling branch September 18, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spawn failure handling: hub chat message, last failure per hub, Sentry once per streak

1 participant