* fix(gateway): bind auth limits to ingress attribution
* fix(gateway): close remaining ingress auth gaps
* fix(gateway): carry attribution into new ingress paths
* fix(gateway): close ingress ownership gaps
* fix(gateway): complete proxy ingress hardening
* fix(gateway): stabilize managed Tailscale ingress
* fix(gateway): make Tailscale cleanup ownership-safe
Refuse reset-on-exit publication until Tailscale exposes an atomic owner-bound cleanup operation, and migrate legacy configs with Doctor.
* fix(gateway): finish ingress ownership repair
* fix(gateway): own managed Tailscale route lifetime
Run managed Serve and Funnel routes as foreground claims tied to the Gateway lifecycle. Retire named Service config through Doctor because Tailscale Services cannot run in foreground mode.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): align Tailscale consumers and build guards
Remove the retired named-service config from Telegram Mini App URL resolution and register the lifecycle worker as an explicit production entry.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): preserve retired Tailscale inputs
Keep Funnel enabled when removing an ignored named-Service setting and accept the legacy positive reset flag as a no-op now that managed routes always follow Gateway lifetime.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): preserve Tailscale route diagnostics
Prefer the actionable foreground CLI failure captured during timeout cleanup, and cover the original delayed-failure ordering.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): reconcile Tailscale ingress with main
Preserve current ingress ownership contracts after the rebase, retire the obsolete device-auth migration check, validate route-owner IPC, and move Tailscale auth coverage onto the managed listener.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* fix(gateway): finish ingress rebase coverage
Unify the rebased net imports and let module-reset WebSocket tests prepare attribution through the same fresh module instance as the handler.
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
* test(gateway): align run-loop server fixture
---------
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
Make the Sessions route and page share the managed session-list owner so startup hydration, reconnects, filters, and mutations cannot schedule duplicate raw roster requests. Preserve last-good rows and retire stale query epochs.
* fix(agents): preserve resolved agentId in model workspace resolution
resolveModelWorkspaceDir re-resolved a default agent instead of reusing
the agentId the caller already authorized, so any plugin call to
runtime.llm.complete with an explicit agentId threw
AgentSelectionRequiredError on multi-agent configs. Thread the known
agentId through resolveModel/resolveModelAsync into workspace
derivation so it is only re-derived when the caller has none.
Fixes#124822
* fix(agents): forward agentId through route-materialization re-resolution
The route-materialization resolver call in prepareSimpleCompletionModel
re-resolved the model without the caller's agentId, so a multi-agent
plugin completion that switches OpenAI routes still fell back to
resolveDefaultAgentId and threw. Forward params.agentId on this call
too, matching the first resolution.
Blacksmith's image tracks an older runner-images snapshot. Measured on a leased
box 2026-08-16, its toolcache holds Node 20.20.0, 22.22.0 and 24.13.0 while this
repo's engines floor is >=22.22.3 and >=24.15.0 -- short by three and two
patches. Every candidate is rejected, so all 306 of 306 sampled jobs fell
through to a nodejs.org download. GitHub-hosted runners carry 24.19.0 and
resolve from the toolcache in about a second, which is why only Blacksmith pays.
Normally that download is 2.6s (p99 3.3s), but ~46 jobs fetch the same 50 MB
simultaneously and the mirror throttles: three of 53 sampled runs had setup-node
medians of 44-93s with maxes to 139s, and because every job pays at once it
lands whole on the wall -- those runs went ~210s to 325s.
Keep the payload in the Actions cache, which Blacksmith serves from its
colocated backend. Measured on Blacksmith: cold 1605ms, warm 77ms.
Restores are prefix-keyed and the save carries the resolved patch. An exact key
would be worse than nothing: cache entries are immutable and an exact hit
suppresses the post-job save, so a floating `24.x` key would pin the first Node
it ever saw and, once the floor advanced past it, every job would restore the
rejected payload and re-download forever. Keying the save on the installed
version lets a newer resolve publish a new entry that later prefix restores
pick up.
A rejected payload is pruned before the replacement installs, because the entry
is saved wholesale and a leftover would ride along in every future save.
Windows keeps its existing path. Proven on Blacksmith across cold, warm, stale
and truncated-binary cases; both guards are mutation-checked.
This stays useful even if Blacksmith refreshes their image: the floor moves
independently of the snapshot, so the gap recurs. The image refresh is still
the better fix and is worth asking them for.
The cron model picker help and the automations docs now say what
operators otherwise learn by surprise on the bill: summaries, triage,
and classification jobs run well on a lighter model, and --fallbacks
covers escalation. Copy-only change; pnpm ui:i18n:verify green, no
baseline drift.
Keep Gateway admission sidecar-gated while surfacing retryable startup progress in TUI and Control UI.
Unify deferred startup ownership and shutdown draining so late workers, plugin services, and sidecars cannot outlive transport teardown.
Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
The finished-turn digest ('🧠 N thoughts · 💬 N notes · 🛠️ N tool calls ·
⏱️ Ns') was synthesized onto three surfaces: Slack's Block Kit card footer
(removed in #123851), Discord's final-answer subtext, and a duplicate Telegram
implementation added to match Discord.
Nothing gated it — no config key for the receipt has ever existed on any
channel — so PR #122976, which deleted it from Slack's native progress surface,
could only ever be a partial removal. Finish it: live progress surfaces keep
their in-flight indicators, and a finished turn leaves the answer with nothing
synthesized appended.
Telegram's window collapsed *into* the digest, so it now retires like every
other surface; Discord already discarded its draft after the final landed. The
core tracker keeps only the live counters Slack's working footer reads.
* fix(logging): scale RSS pressure to runtime limits
Derive Gateway RSS warning and critical thresholds from the smaller of the effective V8 heap and OS process limit. This preserves heap and rapid-growth pressure detection while avoiding routine false alerts on large heaps.
* test(logging): narrow optional heap fixture
* fix(logging): bound process memory sentinels
* test(logging): pin Bun physical memory fixture
* fix(doctor): surface legacy-config copy failures
maybeMigrateLegacyConfig swallowed every copyFile error with a bare
catch whose comment claimed EEXIST-only. EACCES, ENOSPC, and
cross-device failures were silently ignored: the operator's
~/.clawdbot/clawdbot.json exists, migration was attempted and failed,
and doctor proceeded as a clean fresh install with no config, no change
note, and no warning.
Root cause: failure collapsed into the skip-silently success shape. The
catch now rethrows anything other than EEXIST with the source/target
paths in the message; EEXIST (config already at the target) keeps its
skip semantics.
Regression: read-only target dir makes the preflight reject with
'Failed to migrate legacy config' — fails pre-fix (resolved as a clean
run); companion test proves the successful copy path still migrates.
* chore(doctor): annotate the errno assertion for the safety ratchet
* refactor(doctor): narrow the copy error without a type assertion
The assertion-safety ratchet flagged the errno cast; property narrowing
removes the assertion instead of annotating it.
Live verification of user-facing behavior is the default before landing,
with skips requiring a stated infeasibility in the PR; captured
screenshots/videos count as evidence only after the agent inspects them
in frame.
Split Signal approval routing into its concept-owned module and call the shared SDK binding helpers directly.
Reject persisted reaction targets when any allowed decision is invalid or duplicated, rather than retaining a valid subset from a corrupt transient record.