Add Consumer Loop State Reporting

Claude Code·Opus 5.[1m]·nodo·3w ago·4min·1 Checkpoint·3 file changes·+269·9.5K tokens

Base directory for this skill: /Users/nodo/.claude/skills/progress

Daily #progress update

Builds the daily update for #progress (C09MB3TPBFF, entireio.slack.com) from evidence, in Andrea's voice, and stops for review.

Hard rule: never post. Never call slack_send_message or slack_schedule_message for this channel. The only write allowed is slack_send_message_draft, and only after the user has seen the text and said yes. A draft lands in Slack's Drafts & Sent where they edit and send it themselves.

1. Pick the window

Find the last update Andrea posted in the channel:

Ignore short replies and thread messages, look for the last real bulleted update. The window runs from that message to now. If there's a multi-day gap (weekend, OOO), cover the gap and lead with - Catch-up, which is what he does.

Times are Europe/Rome. Get "now" from the shell (date), never assume.

2. Gather evidence

Run these together, they're independent.

Trails (the primary source, most bullets link one). Repos live in /Users/nodo/work/entire/*. For each repo with a .git:

Keep trails whose created_at/updated_at/merged_at fall in the window. Useful fields: url, title, status (draft/open/merged/closed), phase, branch.

Commits, which catch work with no trail and unpushed work:

--all matters: trail work sits on branches, so plain git log (HEAD only) misses it.

Cross-repo backstop, covering repos not cloned locally and pushes from elsewhere: get_entire_my_activity(scope="commits", since=..., until=...) on entire-prod-us. This returns full commit bodies and is very verbose. Read repo_full_name and commit_subject, skip the rest. commit_message is null for native repos, local git fills those in.

Linear, which gives each item its ticket ID and title:

(-P3D or wider for a catch-up. Note: identifier is not a valid field, the ID comes back as id.)

Incidents / firefighting: incident_list(assigned_to=["me"], created_after=..., include=["summary"]). Also worth a look: alerts he chased in #alerts-production (C0B1Y3Y6PFD).

His own words elsewhere. He often links a thread he wrote in another channel rather than re-explaining:

Grab permalinks for substantial design or decision messages. Skip chit-chat.

3. Write it

Read style.md in this skill directory before writing. It has the rules and real examples. Two rules matter most: no em dashes, and be concise.

Group the evidence into 2 to 6 themes, not a flat dump of every commit. Twelve commits refactoring one thing is one bullet with the theme as the headline. Judge by what a reader cares about, not by diff size.

State what changed and why it mattered, not what you touched. "Fixed a tricky bug when a github mirror changes ownership" beats "updated mirror teardown logic".

Every claim needs evidence from step 2 behind it. Don't infer that something shipped because a trail exists, check status. Don't invent ticket IDs. If a trail is still open, say in progress or put it under In progress. Leave out anything you can only guess at. A shorter honest update is the right outcome on a quiet day.

4. Review, then draft

Save it to /Users/nodo/work/entire/progress/<YYYY-MM-DD>.md, one file per update. That also builds a local archive of past posts to check tone against.

Show the full text in the terminal and ask whether to create the Slack draft. Offer that it's easy to adjust: wrong emphasis, a missing meeting, something that shouldn't be public.

Only when they approve:

Then tell them it's waiting in Drafts & Sent in #progress, unsent.

If it returns draft_already_exists, say so. Slack allows one draft per channel, so they need to send or delete the existing one first. Don't work around it by posting.

Notes

  • Slack user ID: U0AJ0FHDXFW. Channel: C09MB3TPBFF.
  • Meetings, reviews, design discussions and OOO notices count as progress. Engineering output is most of it, but a day of unblocking others is worth saying.
  • If the day genuinely produced little, a one-liner is fine and in character (- Working on making NATS more robust :thinkies:).
3w ago·4m·26 calls

Window is yesterday 17:40 → now. Gathering evidence.

b6cccacfeat(jsconsumer): report consume-loop state and retryable start errors A supervised consumer is currently unobservable from outside. Run retries every failure it can and returns only for a configuration the broker will never accept, so an adopter cannot answer either question its operators ask: am I consuming right now, and how long have I been failing. Both facts are already in the runner. This exposes them. - Config.OnConsuming(ctx, bool) — true once the durable is bound and delivery has begun, false when that loop closes (consumer deleted on the server, subscription invalidated, Runner.Stop). Assign an atomic; read it from the health handler. - Config.OnStartError(ctx, err) — each start attempt Run will RETRY, with the error. Permanent failures are returned instead, so they never arrive here: an adopter's failure counter and its fatal path stay describing different events. Also: IsTransientSubscribeErr now knows the modern client's error values. The jetstream package does not reuse the nats.* ones — its ErrStreamNotFound is a distinct type carrying an APIError — so errors.Is finds neither across the two, and a consumer migrated to the modern client had every rollout-ordering failure classified as a FAULT. That is the alert erosion this predicate exists to prevent, arriving through the door it holds shut. A missing CONSUMER stays absent in both spellings, for the same reason nats.ErrConsumerDeleted is: recreating the durable replays the stream's retained backlog, and the log is the only evidence. WHY NOW mirror-pipeline's first migrated consumer (COR-1254 loop 1) reconstructed both facts from outside: a CONSUMER.INFO probe on a ticker, a two-phase cadence, a first-bind flag, a shutdown guard, and a local copy of the error classification — ~130 lines of production code and five of its ten broker tests. Every one of that trail's five review rounds found a defect in that scaffolding; none found one in the migration it was wrapped around. Ten more consumers were queued behind the same copy. With these two callbacks that scaffolding is deleted, not extracted. NOTES OnConsuming is reported from Start (not Run) so a one-shot caller gets it too, and the true is emitted before the ctx watcher is armed — a caller cannot observe the false that follows a close without first having seen the true. Both callbacks run on the runner's own goroutines: documented as must-not-block, and calling Runner.Stop from inside OnConsuming deadlocks. Deliberately NOT a heartbeat. consuming=true says the loop is live, never that a message arrived; a consumer on a quiet stream is healthy. Runner.Drain — the other gap COR-1254 recorded, needed for a worker-pool adopter to stop discarding its prefetch buffer on shutdown — is not here. It changes consume semantics rather than exposing existing state, and belongs with the consumer that needs it (loop 9). TESTS Three, against the embedded broker, each verified to fail for its own reason: dropping the false transition, dropping the start-error report, and dropping the modern classification each turn exactly one of them red. The retryable fixture withholds the STREAM, which is retryable by construction, and also asserts the error it hands back is classifiable — an error IsTransientSubscribeErr cannot recognise would be logged as a fault by every adopter. Gate: build, vet, gofmt -s, go test ./..., go test -race ./..., golangci-lint (0 issues) — all clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019zK85NVAs1Enxh2eFidFpo Entire-Checkpoint: 58d6df711574+269