Skip to content
cccskills
FreePublished on GitHub

storm

Diagnose and CURE storms that wedge the portal — resubscribe/retry storms, NotFound/DeliveryFailure storms, per-instance-hub accumulation, create-in-render loops. A storm makes the process unresponsive → liveness /healthz times out → the pod is pulled from the Service → ingress returns 502 → SIGKILL + restart. Use when a portal pod restarts/502s, memory climbs unbounded, the log volume explodes, or you are reviewing reactive code that could re-fire on every emission. Storms come from exactly two roots: (1) sync-over-async blocking, (2) uncaught / not-forwarded exceptions or delivery failures. Grounded in ErrorPropagationAndWedges, AccessContextPropagation.md; pairs with the /async skill.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12 stars(GitHub 12 + cccskills 0)
Works withKubernetes

Run score Not tested

See how to install

Files included (1)

  • SKILL.md11.2 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

/storm — Storms wedge the portal. Cure the root, never the symptom.

A storm is the same cheap operation re-firing without bound — a resubscribe, a retry, a NotFound re-post, a thread/hub re-activation, a write that fails and is retried. Individually each is fast; together they starve the scheduler / exhaust memory / pin the action block. The process stops responding → the k8s liveness probe /healthz times out → the pod is removed from the Service endpoints → the ingress has no healthy backend → 502 → liveness SIGKILLs (exit 137) → restart. Users see a 502 outage. This must never happen.

Canonical references:

The two roots (there are only two)

Every storm traces to one of these. Find which, fix THAT:

  1. Sync-over-async / blocking. async/await/Task<T>/.ToTask()/.FirstAsync()(blocking)/ .Wait()/.Result/.GetAwaiter().GetResult()/Observable.FromAsync/SemaphoreSlim on a hub action block, grain turn, or Blazor circuit. It parks the scheduler; the awaited message can never be processed → deadlock that presents as a hang. → fix per /async.

  2. Uncaught / not-forwarded exceptions or delivery failures. A write fails (often because AccessContext was lost across a .Subscribe/IIoPool hop → RLS denies), a stream errors, a request gets NotFound/DeliveryFailure — and the error is swallowed (.Catch(Observable. Empty), catch {}, _ => { /* ignore */ }) or retried (a 1 s ticker, a watchdog, a resubscribe, a .Retry()) instead of surfaced. The amplifier turns one mishandled error into a flood. (The 2026-06-08 outage was an initial-state retry watchdog; the 2026-06-25 outage was thread-hub accumulation + a swallowed-and-retried heartbeat read.)

The cure (one principle)

Surface errors until you reach a boundary where you can error gracefully — then error gracefully. Do NOT swallow, and do NOT retry a "shouldn't happen" state. Forward the error up the reactive chain (OnError) until it reaches a real sink: an activity's terminal Status = Failed, the GUI error area / PortalErrorSink, a thread's output cell, a logged warning at a known boundary. Everything before that sink must forward, not absorb.

Band-aids that are FORBIDDEN as "the fix" (they hide the storm): raising a timeout/pool size/retry count; a watchdog/poller that resubscribes; .Catch(Observable.Empty); catch {}; a Clear() "for isolation"; a sleep. See AGENTS.md → "No band-aids".

Diagnose a live/just-happened storm (the procedure)

The portal hung silently while wedged (a deadlocked/starved process stops logging), then SIGTERM at shutdown emits a mass-disposal storm that can rotate the pre-hang logs out of the buffer. Work the timeline:

🚨 Break-glass reads. Restart counts, pod events and the previous container's log are exactly the three questions the control instance's Hosting API does not answer yet (measured 2026-09-08: Hosting/DeploymentStatus and Hosting/LogEntry exist as types with zero instances). Take them read-only through az aks command invoke, name them as break-glass in what you write down, and never turn the cure into a kubectl write — a restart is a Restart Hosting/InstanceAction. Policy: OperatingFromThePortal.md.

# 1. Did the pod restart, and how? exit 137 + "failed liveness probe" = wedge (not OOMKilled).
az aks command invoke -g <rg> -n <cluster> --command \
  "kubectl get pod <portal-pod> -n <ns> -o jsonpath='{.status.containerStatuses[0].restartCount}{\" \"}{.status.containerStatuses[0].lastState}'; \
   kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -30"

# 2. Find the SILENCE = the hang window. Per-minute line counts around the kill.
#    Zero lines for minutes, then a huge burst in the final minute = hung, then mass disposal.
az aks command invoke ... --command \
  "L=\$(kubectl logs <pod> -n <ns> --previous --timestamps 2>/dev/null); \
   for m in 45 46 47 48 49 50 51 52 53 54 55 56; do echo -n \"HH:\$m \"; echo \"\$L\" | grep -c \"THH:\$m\"; done"

# 3. What stormed? Source frequency in the wedge/disposal window; storm counts.
az aks command invoke ... --command \
  "L=\$(kubectl logs <pod> -n <ns> --previous 2>/dev/null); \
   echo \"\$L\" | grep -E 'fail:|warn:' | grep -oE '(fail|warn): [A-Za-z0-9._]+' | sort | uniq -c | sort -rn | head; \
   echo -n 'NotFound: '; echo \"\$L\" | grep -c NotFound; \
   echo -n 'distinct sync hubs disposed: '; echo \"\$L\" | grep -oE 'sync/[A-Za-z0-9_-]+' | sort -u | wc -l"

# 4. Distinct vs looping. Is it N things created (creation storm) or 1 thing re-firing (loop)?
az aks command invoke ... --command \
  "kubectl logs <pod> -n <ns> --previous 2>/dev/null | grep -oE '<the-repeating-token>' | sort | uniq -c | sort -rn | head"

# 5. Is the CURRENT (restarted) container re-accumulating? memory trend + the repeating block.
az aks command invoke ... --command "kubectl top pod -n <ns>; kubectl logs <pod> -n <ns> --since=60m | grep -A1 '<token>' | head -30"

Read the last lines before the silence (grep -v 'THH:<kill-minute>' | tail -50) — the last thing logged before it froze is the trigger. If those are rotated out, the mass-disposal volume (distinct sync/ hubs) tells you the scale of the accumulation, and step 5 tells you whether it's still happening.

Storm taxonomy — tell → cure

StormTell in logs / codeCure
Resubscribe / initial-state retryObservable.Interval/Timer or .Catch that re-subscribes the source on a "shouldn't happen" state; repeated SubscribeRequestDelete the retry; let the error surface. If the initial state is genuinely missing, find why it's dropped/errored.
Swallow-and-retry_ => { /* swallow; next tick retries */ } on a per-tick read; .Catch(Observable.Empty) inside a loopForward the error to a sink; drop the path from the retry set on terminal failure; don't re-read a NotFound every tick.
NotFound / DeliveryFailure[ROUTE] NotFound repeating; ownerless satellite (_Thread/_Activity/… with no real owner); a path advertised before its CreateNode completedNever advertise a node path until Create completes (stamp inside .Subscribe). Anchor satellites under a real owner. Lean on the MeshNodeStreamCache negative cache.
Fan-out to a dead subscriber (#2426/#2546)[ROUTE] Stream-routed delivery to 'portal/…' / 'node/…' has no live subscriber at the change-feed rate for hours after a restart/disconnect; senders are per-node owners; "a NACK has no NACK of its own"Not a retry — the owner's server-side stream is immortal (only an UnsubscribeRequest disposes it). The router's refusal is stamped DeliveryFailure.TargetUnserved, is delivered to a grain-hosted sender over the grain transport, and the owner EVICTS that subscriber's streams (Workspace.EvictClientSubscriptions). Never a negative cache on the delivery path; the LOG is windowed (DeadTargetRefusalLog).
Per-instance hub accumulationthousands of sync/ hubs at disposal; a per-hub Observable.Interval heartbeat that runs until dispose; memory climbingEnsure per-instance hubs (thread/round/sync) are disposed when their round/thread reaches terminal state, not only at mesh shutdown. A live timer must not outlive its purpose.
Create-in-renderStartThread/SubmitMessage/CreateNode inside a .Select/.Subscribe on GetMeshNodeStream(...) that re-fires every emissionMove creation out of the render/subscription; one-shot it (.Take(1)/init) or make it idempotent (deterministic path), guard with DistinctUntilChanged.
Sync-over-async deadlock/healthz timeout with no error; .Result/.Wait()/.ToTask() on a hub/circuitPer /async: compose IObservable + Subscribe; push the leaf to IIoPool.

Where to look first (highest-yield)

Storms cluster where an error crosses an async boundary and isn't forwarded — usually because AccessContext was lost (write fails closed → swallowed → retried). Grep for the amplifiers:

rg -n "Catch\(Observable\.Empty|Catch<[^>]+>\(_ *=> *Observable\.Empty|catch *\{\s*\}|/\* *swallow|/\* *ignore|\.Retry\(" src
rg -n "Observable\.Interval|Observable\.Timer\(|RegisterForDisposal\(_ *=> *sub" src   # unbounded tickers — does the sub die with its purpose?
rg -n "\.Subscribe\(\s*_? *=> *\{ *\}\s*,\s*_? *=> *\{\s*\}\)" src                      # write with both arms empty = swallowed failure

For each hit decide: graceful sink (logged/surfaced at a real boundary → OK) or silent swallow / blind retry (→ fix: surface, or stop retrying a terminal state). When the swallowed thing is a write, check the /async AccessContext rule first — the failure is usually a fail-closed RLS denial from a lost identity, and the real fix is to carry the context, then let any remaining error surface.

Checklist before merging reactive code

  • No blocking async on a hub/grain/circuit (.Result/.Wait()/.ToTask()/.FirstAsync()-blocking/Observable.FromAsync/SemaphoreSlim). → /async
  • Every .Subscribe write has an onError that surfaces or logs at a real boundary — never an empty swallow inside a loop/ticker.
  • No timer/watchdog/.Catch that re-triggers a "shouldn't happen" state. If state is missing, fix the source, don't retry.
  • Writes on .Subscribe/IIoPool/Observable.Create hops carry AccessContext (else fail-closed → swallow → storm).
  • Node paths are advertised only after CreateNode completes; satellites anchor under a real owner.
  • Per-instance hubs / timers are disposed at terminal state, not only at mesh shutdown.
  • If a bound exists (top-N, no-retry, sampling), it's log()-ged — silent truncation reads as success.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

async

Free

Forwarding AccessContext through async/reactive boundaries, and the no-async rule on the actor-model mesh. Use when writing or reviewing any hub-reachable / Blazor-view / agent-round / IIoPool code that creates, reads, or updates a mesh node, OR when a write "silently does nothing" / "Access denied" appears after a .Subscribe / IIoPool / Observable.Create hop. The async/await/ToTask/.Wait/.Result/.GetAwaiter/FirstAsync family and lost AccessContext are the two ways async boundaries cause wedges and storms. Grounded in AsynchronousCalls.md, SyncedMeshNodeQueries.md, AccessContextPropagation.md.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 30, 2026

ci

Free

What CI proves, what it cannot prove, and how to write a gate that cannot pass on no evidence. Use when authoring or changing a GitHub Actions workflow, adding a required check, wiring a satellite repo CI, or reasoning about whether a green run actually covers your change. Covers the absolute no-skip-trapdoor rule (a gate must never carry continue-on-error on its input step nor an if that asks whether a secret is set — GitHub paints a skipped job the same colour as a passed one), the reusable workflow_call jobs satellite repos must call instead of hand-rolling, why runs on main are deliberately never cancelled, and the large body of in-mesh C# that no dotnet build or test ever type-checks.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 30, 2026

debug

Free

Debug a deadlock / hang / timeout in the mesh — a test that times out, a hub handler whose response never arrives, a GetRemoteStream / GetMeshNodeStream / GetQuery that never emits, an agent round or per-instance-hub activation that parks forever. Use whenever something times out and you need to find WHERE the message flow breaks down. The timeout is the symptom, never the bug — raising it just hangs longer. Grounded in DebuggingMessageFlow.md.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 30, 2026

Remove / reset a user (and their partition) on a running MeshWeaver portal (AKS). Use when you need to kick out a user — e.g. a throwaway test account — so they re-onboard from scratch. The normal delete API REFUSES a user's home ("user partition root … cannot be deleted"), so deletion is a Postgres-schema drop, not an MCP call. Covers where a user's data actually lives (per-user PG schemas named by BOTH id AND email, e.g. `rbuergi` + `rbuergi@systemorph.com`), how to reach PG (the connection string is inline in the portal's `ConnectionStrings__memex` env — NOT the `POSTGRES_PASSWORD` secret), the substring-match footgun (`buergi` also matches admin `rbuergi`), the in-memory resurrection gotcha (a recycle re-saves the node — a portal restart is what actually clears it), and the half-onboarded case where the user has NO schema at all (in-memory only → restart is the whole fix).

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 30, 2026

deploy

Free

Deploy MeshWeaver — the two routes (shared AKS cluster, and Azure Container Apps via the Aspire test/prod modes) and the traps that make a deploy look done when it is not. Use when rolling a code change onto a portal, standing up or patching an environment, or verifying that what is running is what you built. FIRST RULE (maintainer, 2026-09-08): on AKS an operation is a Hosting/InstanceAction on the control instance (Roll, Restart, Suspend, Audit, Reconcile, …) executed by the in-cluster operator — az aks command invoke / kubectl are break-glass, never the procedure. Also covers the bootstrap build-image / set-image / restart sequence the operator runs for you, why an env deploy.sh is first-time setup and not a code-update path, why the database migration is a run-once Job that a helm upgrade runs — never a Deployment to roll — and the DbVersionGate that makes a portal refuse to serve ahead of its schema.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 30, 2026

gui

Free

Build or review ANY MeshWeaver UI — layout areas, editable views, forms, tables, content editing, side-panel composers — by wiring the framework's EXISTING data-binding pieces, NEVER hand-rolling. The one rule is ALWAYS DATA-BIND, especially editable views: the backend layout area declares WHAT to render (a UiControl tree + node PATHS), and every value read / write-back happens on the GUI side through the per-node IMeshNodeStreamCache. Hand-woven binding — replicating a node into a /data/{id} copy + a save subscription, emitting HTML strings, hand-built selects/checkboxes, .Take(1) on a live stream, async/await/QueryAsync in a layout area — is the bug class behind "awaiting first data" / "renders empty" / a stuck editable view. Use when writing or reviewing a layout area, control, editor, form, table, or any data-bound view. Grounded in src/MeshWeaver.Layout/DataBinding/, Doc/GUI/DataBinding.md + Editor.md, the Edit extension (EditorExtensions.cs), and the node-bound editor controls.

The English summary is being prepared. Showing the original description.

Systemorph/MeshWeaver12Updated Oct 30, 2026

See all skills by Systemorph

Report a problem with this skill