Skip to content

release: v0.35.4#680

Merged
Nathan Schram (nathanschram) merged 57 commits into
masterfrom
dev
Jul 23, 2026
Merged

release: v0.35.4#680
Nathan Schram (nathanschram) merged 57 commits into
masterfrom
dev

Conversation

@nathanschram

Copy link
Copy Markdown
Member

Release PR for v0.35.4 (56 commits on dev since v0.35.3; full rc1–rc14 cycle). Merging this to master is the single release gate — it triggers auto-tag-on-master.yml (v0.35.4 tag) → release.yml (PyPI publish via OIDC + GitHub Release). See CHANGELOG.md for the full entry.

Highlights

Reliability (the empty-resume / post-result-lifecycle milestone)

Security

Features

Engines / plan mode

Release CI

Version / docs

Integration-test status

  • rc14 (code-identical to this stable cut — only the version string differs) has a passing @untether_dev_bot attestation marker from 2026-07-22.
  • No literal 0.35.4 attestation marker exists yet. That gates scripts/fleet-rollout.sh (production-host rollout), not this PyPI publish. Recommend writing scripts/run-integration-tests.sh 0.35.4 --manual (rc14 coverage) before the fleet roll — happy to do that on request.

⚠️ Merge authority

Opened by Claude Code as the release artifact; the merge is reserved for Nathan Schram (@nathanschram) (release-guard + CODEOWNERS). This PR was not merged and will not be merged by Claude Code.

🤖 Generated with Claude Code

… bg-handle lifecycle (#374) [0.35.4rc1] (#575)

* fix(runners): v0.35.4 Batch 1 — Pi diagnostics, Pi AutoRetry, Claude bg-handle lifecycle

#565 (fix, pi): surface Pi stderr and distinguish a startup/early-exit crash
(rc=0, zero translated events) from a truncated stream on the no-agent_end
fallback path. Resolves the *diagnostics* gap the issue was re-scoped to — the
original "resume is broken" mechanism did not reproduce (transient MCP-cold
startup), so fix options A/B/C were deliberately NOT implemented. Threads
stderr_lines through the polymorphic stream_end_events (base + 6 runners);
logs had_events/resumed on pi.stream.no_agent_end.

#460 (fix, pi): translate auto_retry_start/auto_retry_end (schema-defined but
never handled) into note actions with a stable retry_N id across the pair.
Watchdog "retry is CPU-active" suppression deferred (delays << stall threshold;
translated events already refresh the idle timer).

#374 (fix, claude; Monitor-only subset): clear a background handle only on a
*terminal* tool_result. Fixes Monitor interim-result dropping the
stall_monitor_active suppression. New _is_terminal_tool_result + is_terminal
gate. A Monitor's results are arbitrary command stdout, so terminal detection is
is_error OR the timeout_ms deadline — NOT text-marker matching (which would
false-clear on a build printing "Done"). The deadline is the bounded, reliable
backstop (no leak). Bash-bg/Agent-bg/ScheduleWakeup/RemoteTrigger keep
clear-on-result; their terminal-detection (KillShell, exit, deadline sweeps) is
deferred to v0.35.5 to avoid the inverse hang.

Tests: test_pi_runner +8, test_claude_runner +5 Monitor-lifecycle (interim kept
incl. "Done"-in-stdout, error/expired/unknown-deadline/foreground clear). Docs:
pi untether-events §4.6. Full suite green, 82.70% coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: staging 0.35.4rc1

First rc of the v0.35.4 milestone — Batch 1 (Pi diagnostics #565, Pi AutoRetry
#460, Claude bg-handle lifecycle #374). Bumps version + lockfile so the dev
push publishes a fresh artifact to TestPyPI (skip-existing would otherwise skip
0.35.3). Changelog v0.35.4 section already carries all three issues.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ase CI, drain (#576)

Security & ops hardening batch (5 issues, file-disjoint):

- #381 security: SSRF-validate voice_transcription_base_url at the
  transcribe_voice chokepoint (async/DNS) + sync fast-fail at config load;
  new optional voice_transcription_url_allowlist for private endpoints.
- #382 security: refuse to bind the webhook server when any webhook has
  auth="none" on a non-loopback host (initial bind + hot-reload); opt in via
  [triggers] allow_unauthenticated_webhooks.
- #459 lockfile: replace os.kill(pid,0) liveness with fcntl.flock(2) advisory
  lock — kernel auto-releases on death, ending the PID-reuse crash-loop. Lock
  fd kept open for process lifetime, non-inheritable.
- #376 release: push the auto-created tag with RELEASE_TAG_PAT instead of
  GITHUB_TOKEN so the tag push triggers release.yml (single-gate flow).
  Requires Nathan to create the secret + drop the pypi env reviewer.
- #559 shutdown: shorten drain to 10s when the sole active run triggered the
  restart (#547 self-restart deadlock) + bounded outbox flush before close so
  an already-queued final message isn't dropped.

Tests: +~440 lines across 9 test files. Full suite green (2783 passed,
82.65% coverage). CHANGELOG v0.35.4 (unreleased) section + FAQ Q9 touch-up.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump 0.35.4rc1 → 0.35.4rc2 so the v0.35.4 Batch 2 security/ops work (#381
#382 #459 #376 #559, merged in #576) publishes as a fresh TestPyPI wheel —
rc1 already exists on TestPyPI (skip-existing skipped the unchanged version).

No code/changelog change (rc versions skip changelog per validate_release).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.36.0 to 4.36.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@7211b7c...87557b9)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.36.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@de0fac2...df4cb1c)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.1.0 to 8.2.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](astral-sh/setup-uv@0880764...fac544c)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.36.1 to 4.36.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@87557b9...8aad20d)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.36.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](softprops/action-gh-release@b430933...718ea10)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.3 to 7.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@df4cb1c...9c091bb)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.36.2 to 4.36.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@8aad20d...54f647b)

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.2.0 to 8.3.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](astral-sh/setup-uv@fac544c...d31148d)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.36.2 to 4.36.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@8aad20d...54f647b)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…p-audit advisories (#604)

- aiohttp 3.13.5 → 3.14.1 (PYSEC-2026-237, PYSEC-2026-2104…2113)
- click 8.3.1 → 8.4.2 (PYSEC-2026-2132)
- msgpack 1.1.2 → 1.2.1 (GHSA-6v7p-g79w-8964)
- pip 26.0.1 → 26.1.2 (PYSEC-2026-196)
- pydantic-settings 2.12.0 → 2.14.2 (GHSA-4xgf-cpjx-pc3j)

pip-audit has been failing every PR since these advisories landed (dev's
last green CI predates them, 2026-06-02).

Compatibility fix: aiohttp 3.14 calls resume_reading(resume_parser=…) on
the StreamReader protocol, breaking the _NullProtocol shim used to parse
multipart bodies from cached bytes (4 test failures, webhook multipart
uploads would 500 in production). Both shim methods now accept arbitrary
arguments.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… exit (#602)

- #591: deliver the final Telegram message the moment a successful
  CompletedEvent arrives instead of waiting for the run generator to
  return (which blocks on proc.wait() behind lingering MCP children —
  up to the full 600s post-result watchdog, one answer lost to /cancel).
  Bridge-side _deliver_final closure is invoked early from
  run_runner_with_cancel and idempotently from the post-return flow;
  note_final()/_finalizing suppress progress repaints and the #470
  closing notice so nothing overwrites the delivered answer. Cancel and
  error paths after delivery are logged, not rendered.
- #591: new [watchdog] post_result_limbo_grace (default 60s, 0=off) —
  the post-result subcountdown SIGTERMs after the grace instead of the
  full timeout when the session is fully quiescent (no pending
  control/ask requests, no live background work per
  has_live_background_work), releasing MCP-held RSS/TCP promptly.
- #599: manage_subprocess now awaits proc.aclose() on every exit path
  (shielded, suppressed) so the asyncio subprocess transport is released
  when the run ends instead of at interpreter shutdown
  ("RuntimeError: Event loop is closed" in BaseSubprocessTransport.__del__).

Tests: early delivery while generator open, cancel-after-delivery keeps
answer, ok=False stays on post-return path, note_final no-repaint,
limbo grace fires/deferred-by-bg-work/0-disables, aclose on clean +
kill paths + error suppression.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ron counts (#603)

- #600: skipped outbox directories are moved into
  .untether-outbox/.skipped/ once (name collisions get _1/_2 suffixes),
  logged as outbox.skipped_dir_archived, and the user-facing skip notice
  now says where the directory went. Previously they were re-scanned,
  re-skipped, and re-logged on every run forever (#524 follow-up gap) —
  including outboxes containing ONLY a directory, which never even
  reached cleanup. Archival is gated on the same outbox_cleanup flag as
  sent-file deletion; the graveyard itself is excluded from scans.
- #601: triggers.enabled now logs crons=<manager active count> plus
  crons_configured=<raw TOML entries> so it agrees with
  triggers.manager.updated / triggers.cron.started at startup. The
  previous raw count read as "N crons failed to load" during triage when
  the delta was just run_once entries already spent.

Tests: graveyard move + reason rewrite, second-run silence, only-dir
outbox archives, cleanup=false untouched, collision suffix, graveyard
excluded from scan; manager active-vs-configured divergence semantics.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… post-exit orphan sweep (#605)

- New reap_orphaned_group(): after the subprocess leader exits (including
  clean rc=0 — the fleet-wide 1-leak-per-run shape), sweep surviving
  process-group members (SIGTERM → 2s grace → SIGKILL) plus any
  descendant PIDs captured while the leader was alive. Wired into
  manage_subprocess teardown for all engines; logs
  subprocess.orphans_reaped. Config [watchdog] reap_orphans (default
  true per #573's session-end cleanup policy, OWASP ASI08/ASI10).
- New signal_pid_group(): pid-based twin of _signal_process (#275
  descendant snapshot + killpg + per-PID delivery). Replaces bare
  os.killpg in the Claude post-result subcountdown SIGTERM/SIGKILL and
  the base-runner liveness-kill / died-without-completion paths — those
  missed MCP chains that re-parented into separate sessions/pgroups
  (nsd: 25 children survived even systemd's kill sweep).
- ClaudeStreamState.orphan_pid_snapshot: descendants captured at
  reader-done and refreshed at limbo detection, so late-spawned leakers
  (invisible to the first limbo snapshot per #590 evidence) are reaped.
- contrib/untether.service: optional MemoryHigh/MemoryMax guidance for
  memory-constrained hosts (#589) — the leak fix removes the pressure
  source; the ceiling contains any future regression.

Tests: group+descendant delivery, empty-group no-op, SIGTERM→SIGKILL
survivor escalation, snapshot-escapee kill, manage_subprocess wiring +
reap_orphans=False, limbo snapshot refresh; existing killpg tests
guarded against real /proc descendant reads.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…597 image/document blocks (#606)

- #595: catalog_staleness severity split — status=pending at system.init
  is a startup race, now INFO (catalog_staleness.pending, per-run dedup);
  needs-auth/failed keep the WARNING but dedup across runs via a
  process-lifetime (server, status) registry. The per-state dedup set
  reset on every subprocess spawn, multiplying a few broken connectors
  into ~2,930 WARNINGs/48h fleet-wide (96% of all nsd warnings).
- #596: a 0-turn / 0-API-ms / empty-answer completion with ok=True (the
  upstream no-op resume shape) now logs runner.empty_result WARNING and
  delivers a user-visible note ("engine returned an empty result … resend
  or /new") instead of a bare error header with no body. Gated on usage
  being explicitly reported so engines without usage never trip it.
- #597: schema accepts image + document content blocks (Read on binary
  media echoes them inside user-role messages; jsonl.msgspec.invalid x23
  on nsd, #489 family). Permissive source dict — payload never rendered;
  both translate loops already fall through safely. Cheatsheet updated.

Tests: severity split, cross-run dedup + suppressed log, pending stays
per-run; anomaly note delivered, turns>0 not flagged, no-usage not
flagged; image/document decode in user + assistant bodies.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…l enforcement (#607)

- #592: new [watchdog] pre_result_silence_timeout (default 3600s, 0=off).
  A run whose stream goes silent forever BEFORE its first result was
  previously unbounded — the post-result watchdog only arms after a
  result and liveness never escalates an alive-but-idle process (8-day
  zombie Claude subprocess on mac, ~75% of that host's log volume in
  idle ticks). The watchdog's pre-result branch now kills the subprocess
  (descendant-aware, SIGTERM→grace→SIGKILL) once the cap elapses with no
  stream output, SUPPRESSED while permission/ask requests are pending
  (plan-approval waits stay safe per #526/#527) or background work is
  live. The reason is appended to stderr_capture so the run's error
  message explains itself (#575 stderr-excerpt machinery).
- #593a: ClaudeRunner.run_impl now sets self.last_pid (base runner did;
  the override didn't) — the bridge's thread_pid() early-poll works for
  Claude, so stall diagnostics get a PID even when no StartedEvent ever
  arrives (the pid=None process_alive=None blind spot).
- #593b: stall auto-cancel now ENFORCES teardown: after cancel_event is
  set, _enforce_cancel_teardown polls up to 30s for natural death, then
  kills the subprocess directly (descendant-aware; shielded with an
  early-exit poll). Observed: 14m52s between stall_auto_cancel and
  handle.cancelled on nsd during the OOM crisis.

Tests: silence cap kills + reason in stderr, suppressed by pending
request / live wakeup, 0 disables; enforcement escalates / no-ops on
natural death / logs without pid; autouse neutraliser so existing
auto-cancel tests with fake PIDs never probe real processes
(cancel_enforcement marker registered).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…not-modified as no-op (#608)

- HttpBotClient records the failure reason per (method, chat_id,
  message_id) on every failure path (API error description, HTTP status
  + body excerpt, network error class) in a bounded fetch-and-clear
  store; TelegramClient.pop_edit_error() exposes it for edits.
- transport.edit.failed now logs error=<reason> — previously the
  Telegram description was only visible in a separate, uncorrelated
  telegram.api_error line, making the sl aq-callback failures (#598)
  undiagnosable from logs.
- "message is not modified" is normalised to an info-level
  transport.edit.noop returning the original ref — the edit's intent is
  already satisfied (#364 family), so it must not read as a failure.

The sl 3/3-deterministic aq keyboard-clear failure does NOT reproduce on
the dev bot (full ask-mode flow verified: buttons, answer, keyboard
clear, final answer all clean). The enriched log pins the actual reason
on the next fleet occurrence; if it turns out to be not-modified, the
noop path already absorbs it.

Tests: reason recorded+popped, store bounded, failed-edit log carries
error, not-modified noop returns ref, bots without pop still log.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…rrors (#585)

* feat(voice): #584 harden transcription against transient connection errors

Voice transcription surfaced transport-level failures from the Groq/
OpenAI-compatible API to the user as the opaque "Connection error."
string. These are almost always brief, transient network / provider-edge
blips (confirmed on channelo: 2 APIConnectionError events ever vs ~190
successful transcriptions; key/config/network/certs/SDK all healthy and
the transport works in isolation).

- widen AsyncOpenAI max_retries 2 -> 4 so a sub-~15s blip self-heals
  before reaching the user
- special-case APIConnectionError (incl. subclass APITimeoutError) with
  an actionable hint instead of "Connection error."
- fix unreachable except TimeoutError branch (subclass of OSError) by
  ordering it before the OSError handler

Adds 4 tests covering the connection-error hint, timeout-subclass path,
non-connection OpenAIError sanitisation, and the now-reachable stdlib
TimeoutError branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(voice): #594 surface endpoint + cause in transcribe errors; validate api_key header-safety

- openai.transcribe.error now logs the resolved endpoint
  (base_url or "openai-default") and the exception __cause__ —
  APIConnectionError's bare "Connection error." hid the real failure
  (the channelo outage was an illegal Authorization header from two
  concatenated keys, undiagnosable from logs). The timeout/error/
  unexpected log sites gain the endpoint field too.
- voice_transcription_api_key is validated at config load: control
  characters (embedded newline/tab from concatenated or line-wrapped
  key material) and non-latin-1 characters are rejected with a
  diagnosable ConfigError instead of surfacing hours later as a
  connection error on every voice note (fail-fast precedent: the #381
  base-url SSRF validator on the same surface).

Tests: endpoint+cause captured in log, default-endpoint marker,
newline key rejected, non-latin-1 key rejected, normal key unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Carries the 2026-07-13 fleet-audit fixes (#589-#601 clusters):
early answer delivery + limbo grace (#591), transport close (#599),
MCP child reaping (#590/#589), pre-result silence cap (#592), cancel
enforcement + PID threading (#593), catalog warning flood (#595),
empty-result surfacing (#596), image/document schema blocks (#597),
edit-failure observability (#598), voice hardening + observability
(#584/#594), outbox graveyard (#600), cron count alignment (#601),
and the pip-audit dependency bumps.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](softprops/action-gh-release@718ea10...3d0d988)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.3.0 to 8.3.2.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](astral-sh/setup-uv@d31148d...11f9893)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.36.3 to 4.37.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@54f647b...99df26d)

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.36.3 to 4.37.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@54f647b...99df26d)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…final delivery (#615)

Two-part fix for the cancel-during-post-result-window race:

1. run_runner now holds the runner.run() generator and acloses it in a
   shielded scope in its finally. Abandoning the async-for mid-body (e.g.
   /cancel while on_completed awaits) previously left the generator
   suspended at a yield inside run_impl's anyio task group; the event
   loop's asyncgen finalizer then threw GeneratorExit from a foreign
   task and the task group raised 'Attempted to exit cancel scope in a
   different task than it was entered in' as an unretrieved exception.

2. The #591 early final delivery is now shielded (bounded 15s) so a
   /cancel landing mid-send can no longer cancel delivery after the
   message hit the wire but before final_delivery['sent'] was recorded —
   which produced a spurious 'cancelled' render on top of the delivered
   answer via the #598 edit-failed fallback.

Regression tests reproduce the exact production traceback pre-fix.

Fixes #614

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
sl (@hetz_sl_bot, semantic-librarian) was outside the fleet scripts and
required manual upgrades. Adds sl to ALL_HOSTS with systemd restart/
postcheck (install manager auto-detected — uv on sl), and updates the
host enumerations in CLAUDE.md, release-discipline, dev-workflow, and
dev-instance docs from 4 to 5 hosts.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
rc4 carries the #614 cancel-race fix (#615) and the sl fleet-host
addition (#616) merged to dev earlier today.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The 15s bound from #615 expired mid-flag-set on a 4-chunk final under
group-chat outbox pacing during the rc4 integration gate, re-creating
the spurious 'cancelled' render after a fully delivered answer. 60s
covers multi-chunk deliveries plus a 429 retry while still bounding a
wedged transport.

Fixes #618

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
rc5 supersedes rc4 (never fleet-rolled): adds the #618 delivery-shield
bound fix on top of rc4's #614/#616 content.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
GitHub push protection blocked a push on 2026-07-18 because a `git add -A docs/`
swept in an untracked local file containing a Groq API key.

Triage (all verified):
- never committed — `git log --all -S<key>` and `git grep` both clean
- only 1 of 89 untracked files affected — scanned for gsk_/sk-ant-/ghp_/
  github_pat_/xoxb-/AWS/PEM patterns
- NOT the live key: fingerprint differs from the current value in BWS and in
  both untether.toml files, consistent with the 2026-07-13 rotation

So this was a working-tree-only exposure of an already-rotated credential. The
raw value has been redacted from the file in place, and the file is now ignored
so it cannot be swept in again.

Deliberately NOT ignoring docs/tests/ or docs/audits/ wholesale — 5 files in
those trees are tracked, so a blanket rule would misrepresent what is versioned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude Code runs subagents in the background by default — the tool
contract reads "Subagents run in the background by default; ... Pass
`run_in_background: false` for a synchronous run" — so the flag is
normally ABSENT from the Agent/Task tool input.

`_register_background_handle` gated on `bool(raw_input.get(
"run_in_background"))`, which read omission as *foreground* and
therefore never registered a real subagent. Measured on nsd
2026-07-18: 11/11 Agent calls across the 5 quarantined sessions omit
the key; observed shape is `{"description":…, "subagent_type":…}` and
the tool_result ~25ms later reads "Async agent launched successfully."

Failure chain: no handle -> has_live_background_work() False -> the
post-result idle watchdog applies the 60s limbo grace instead of the
600s timeout -> SIGTERM of a subprocess whose subagents are still
working -> #632 quarantines the healthy session on
forced_teardown_after_result -> the user's next message diverts to a
fresh, contextless session. Runs without subagents exit cleanly and
were never affected, matching the reported negative control.

Fix: `_agent_runs_in_background()` — background unless the caller
explicitly opted out with literal False. Over-registering is the safe
direction (bounded by the 600s post-result ceiling and the 900s
BG_AGENT_MAX_KEEP_S age-out); under-registering force-kills live work
and poisons the session. Bash is deliberately untouched — its
run_in_background flag is genuinely opt-in upstream.

Quarantine semantics are unchanged: once a process has actually been
force-killed after a result its upstream turn may dangle regardless,
so the false positive is fixed at classification, not by weakening
the safety net.

Two tests that locked in the inverted contract now express the
foreground case with an explicit `run_in_background: False`.

Root cause independently validated with gpt-5.6-sol via pal debug.

Verified on @untether_dev_bot with 3 live background subagents: the
subprocess passed through limbo untouched and exited naturally rc=0
(previously SIGTERM rc=143 at the 60s grace); no quarantine, and the
follow-up message resumed the same session, recalling both the
codeword and all three subagents' findings.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ships the #646 default-background subagent registration fix (PR #648)
to TestPyPI. The rc8 merge could not publish it — the version was
unchanged, so the upload hit "400 File already exists" and was
swallowed by skip-existing, leaving the 18 July rc8 wheel in place.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#646 liveness-aware ceiling + announced handoff, #593 diag enrichment (#651)

* fix: post-result lifecycle batch — #650 silent reap after delivery, #647/#646 liveness-aware ceiling + announced handoff, #593 diag enrichment

#650 — watchdog auto-cancel alarmed the user after a successful delivery:
- ProgressEdits: the process_dead auto-cancel arm now checks
  stream.did_emit_completed first. A dead subprocess whose run already
  emitted its CompletedEvent is a normally-completed run being reaped
  late (the detector was racing the subcountdown's returncode poll) —
  reap through the same cancel machinery but silently: INFO
  progress_edits.reaped_after_delivery, no WARN, no user-facing
  "session appears stuck (process_dead)" notice. #614 made the
  user-initiated cancelled-after-delivery path quiet; this makes the
  watchdog-initiated variant actually quiet too.
- claude.py subcountdown: periodic subcountdown_tick INFO (~30s,
  tunable) closes the observability blackout between the one-shot
  limbo_detected warning and loop exit.

#647/#646-outcome — 600s post-result ceiling killed live subagent work:
- The subcountdown deadline is now liveness-aware: while
  has_live_background_work() is true and /proc evidence does not show
  the tree demonstrably idle, the SIGTERM is deferred (re-checked each
  poll). Bounded twice: bg handles age out at BG_AGENT_MAX_KEEP_S and
  the hold never exceeds watchdog.post_result_bg_max_hold (default
  1800s, 0 disables). Upstream runs subagents in the background by
  default since Claude Code v2.1.198 and never signals their completion
  on stream-json, so /proc is the only available evidence.
- sigterm_after_timeout now logs live_background_work / bg_hold_extended
  / cpu_active / tree_active — the "tracked and killed anyway" cohort is
  identifiable regardless of which resume-divert label lands (#647
  monitoring correction).

#647 — follow-up during live background work no longer silently amnesic:
- wait_for_session_handoff logs entry/exit (the wait could absorb
  minutes with no journal trace).
- handle_message: when the base 30s handoff wait times out and the owner
  has live background work (new _SESSION_BG_STATE registry +
  session_live_bg_count()), the user is told why the reply is delayed
  and the wait extends (condition-based, bounded by new
  auto_continue.session_handoff_bg_timeout_s, default 600s).
- Every fresh-session divert is now announced: handoff_timeout,
  entry-time quarantine, and the new quarantined_during_handoff re-check
  (the rc9 incident showed the ceiling can quarantine the owner while
  the handoff wait is in flight; resuming it would poison the next turn).

#593 — diagnostics no longer blind at the cancel decision:
- last_seen_alive_s tracked and attached to stall_detected and
  stall_auto_cancel logs (all liveness fields collapse to None once the
  process is gone; this records how recently it existed). The pid
  threading + teardown-enforcement halves of #593 were verified already
  fixed by #607 (last_pid at spawn, _enforce_cancel_teardown).

Tests: 11 new (2 ceiling, 1 tick, 1 registry helper, 2 process_dead
gate, 5 handoff policy); full suite 2977 passed, 1 known
order-dependent failure (#641) unchanged, passes in isolation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: staging 0.35.4rc10

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…e broke sl/mac rolls (#652)

uv's HTTP cache serves a pre-upload copy of the TestPyPI simple index, so
'uv tool install --force untether==X' fails with 'no version of untether==X'
minutes after a publish even when the remote index already has the wheel.
Bit the rc6 roll (worked around manually) and the rc10 roll (sl + mac
FAILED, pipx hosts unaffected). --refresh forces revalidation of cached
index data per invocation; install cost is unchanged materially.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…653 limbo_detected level, #654 queued-wait notice (#656)

* fix(claude): post-result UX batch — #655 busy-aware limbo grace, #653 limbo_detected level, #654 queued-wait notice

- #655: the #591 limbo-grace cap now consults cpu_active/tree_active —
  a demonstrably busy post-result process with no registered background
  handles falls through to the full timeout_s instead of being SIGTERM'd
  at 60s, quarantined, and diverted to a fresh contextless session
- #653: runner.limbo_detected logs INFO when the same evaluation shows
  live background work or a busy process tree; WARNING reserved for the
  genuinely-quiescent stuck case; event enriched with
  live_background_work/cpu_active/tree_active fields
- #654: queued follow-ups behind a lingering post-result Claude session
  now say why they're waiting (background-task count, context carry-over,
  /cancel hint) instead of sitting on a bare "queued" for up to 30 min

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(claude): #655 end-to-end check with a real busy subprocess and real /proc CPU accounting

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: staging 0.35.4rc11

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…#659/#475/#641) (#660)

* fix: 0.35.4rc12 batch — #657 bare rate-limit latch, #638 voice language hint, #572 Type-A auto-retry, #570 cooldown retirement, #659 plan-input outline gate, #475 resolved model hint, #641 test hang-guard

- #657: bare rate_limit_event (all timing fields null) now latches DEFAULT_BARE_RATE_LIMIT_WAIT_S=60s with retry_after_source="default" so awaiting_rate_limit_retry() is directionally correct; estimate shown as "waiting to retry (~60s)"
- #638: new [transports.telegram] voice_transcription_language ISO-639-1 hint passed to the Whisper API language param; hot-reloadable; validated at config parse
- #572: [watchdog] stream_idle_auto_retry (default off) + stream_idle_max_retries — bounded auto-resume for Type-A mid-generation stream-idle timeouts; Type-B never retries; signal-death + cost-budget guards; claude.stream_idle.auto_retry structured log
- #570: progressive-cooldown workaround retired — upstream ExitPlanMode immediate-retry loop verified fixed on CLI 2.1.215; text-based outline gate (mark_outline_pending) survives
- #659: ExitPlanMode plan input now satisfies the outline gate — on plan-file CLIs no chat text is written and the gate deny-looped until Claude gave up; plan body renders as the standalone outline message
- #475: /config Model line resolves the engine's real default (opencode/pi settings files) via get_engine_default_model(), placeholder hints kept as fallback
- #641: 2s anyio.fail_after hang-guards flaked on cold coverage runs — raised to 30s (root cause was timing, not test-order state)
- #639 (out-of-repo): watcher event-set + msgspec dedup signature documented in contrib/README-issue-watcher.md; script deployed to all 5 hosts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tests): #641 — raise remaining fail_after hang-guards to 30s

The CI 3.12 runner reproduced the same flake class in
test_run_main_loop_routes_reply_to_running_resume (2s guard expired under
coverage on a loaded runner). All run_main_loop hang-guards in this file
are now 30s — they exist to catch hangs, not to race the loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ync counts and config keys (#661)

- CLAUDE.md: test count 2914→3010 + drifted per-file counts; Pause & Outline bullet reflects #659 plan-input gate + #570 cooldown retirement; watchdog bullet gains #657 bare rate-limit latch + #572 stream_idle_auto_retry keys; hot-reload bullet names voice_transcription_language
- docs/how-to/plan-mode.md: "Progressive cooldown" section rewritten as "The outline gate" (plan-input hold-open + no-outline auto-deny; historical note on the retired 30-120s escalation)
- docs/how-to/voice-notes.md + docs/reference/config.md: voice_transcription_language documented (#638)
- docs/faq/faq.md: feature-list phrasing updated (cooldown → Pause & Outline plan gate)
- untether-architecture skill: claude_control.py row cooldown→outline-gate wiring

Point-in-time records (docs/plans, docs/audits, docs/research, promo archives) deliberately untouched.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The v0.35.4 section documented only rc1, rc2 and the rc6 audit batch (12
entries). rc3, rc4, rc5 and rc7-rc12 were entirely undocumented — roughly
38 shipped issues, including the headline work of the cycle: the
empty-resume quarantine-and-fresh recovery, the post-result lifecycle
batch, the concurrency-aware OOM guard, and the rc12 interactive batch.

12 -> 50 entries across fixes / changes / tests / docs. Date corrected
2026-05-30 -> 2026-07-20.

Verified before commit:
- 52 cited issue numbers, all confirmed real issues (0 PR numbers), so
  validate_release.py will not trip on a PR reference
- 0 entries missing an issue link
- 0 shipped issues left undocumented; the only uncited refs in the
  v0.35.3..dev range are prior-milestone context refs, the v0.35.5
  deferral #527, and three upstream claude-code numbers

Two deliberate choices:
- #374 amended in place rather than duplicated, and now records that
  Bash-bg/ScheduleWakeup/RemoteTrigger terminal-detection stays deferred
  to #573
- a header comment records that rc8's TestPyPI publish was a silent
  no-op (version unchanged -> skip-existing swallowed the 400), so the
  rc8 commits first executed on fleet hosts as rc9; "verified in rc8" is
  always wrong

Follow-up to #661.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@9c091bb...3d3c42e)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@99df26d...7188fc3)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@99df26d...7188fc3)

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.14.0 to 1.14.1.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](pypa/gh-action-pypi-publish@cef2210...ba38be9)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-version: 1.14.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat(commands): Phase 0 foundation for agentic loop commands

Commit the previously-untracked /debug 8-step bundle and lay the shared
foundation for the agentic loops & slash-command suite (plan:
docs/plans/agentic-loops-and-commands/README.md, Phase 0).

- track .claude/commands/{debug.md,debug/} (was ?? untracked)
- harden debug/step-evidence.md: mandatory redaction pass (tokens/keys/env/
  chat-content/fleet identifiers) + output-size bound on evidence dumps +
  a /handover pointer (no change to the investigation flow)
- add docs/LOOPS.md — the loop registry (L1-L9 agentic + A1-A6 automated),
  honest build-status per loop
- add .claude/rules/workflow-commands.md — routing table + 7 cross-cutting
  rules cited by every workflow command
- .gitignore: incoming/{*-runs,handovers,docs}/ (command run artefacts)
- CLAUDE.md: register the workflow-commands rule

No behaviour change to /debug. Foundation only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(commands): Phase 1 — /fix defect sweep + kaizen loop

Production action-half + continuous improvement (plan:
docs/plans/agentic-loops-and-commands/README.md, Phase 1).

- /fix — action-authority sweep over the /debug 8-step engine: rank the open
  actionable-defect list, fix to a verified hand-off (branch off dev, minimal
  fix, pytest+ruff, changelog, PR to dev, needs-verification). Runaway cap
  (>8 → top 5 + queued-next-run; >15 → STOP diagnosis-only) + batch-cohesion
  rule (never co-batch independent high-risk state machines) + PR #660 table body.
- /kaizen — session-end capture of 0-3 evidence-linked process learnings on a
  lazily-created [kaizen] collector; read-only except ONE comment; 0 is valid.
- /kaizen-review — weekly propose-only promotion (gate + hierarchy); never
  auto-edits rules/hooks/CLAUDE.md/code.
- .claude/rules/kaizen.md (thin slice) + docs/kaizen/README.md (full policy).
- .claude/hooks/kaizen-stop-nudge.sh (Stop-hook nudge; Untether/stop-loop-safe;
  transcript-driven substantive/friction gate) + test harness (10/10 pass).
  hooks.json wiring is a propose-to-Nathan step; /kaizen self-invokes until then.
- docs/LOOPS.md: L6/L7/L8 → available. CLAUDE.md: register kaizen rule.

Live slash-command dry-runs (/fix --dry-run, /kaizen, /kaizen-review) are
operator follow-ups — they make confirm-gated GitHub writes and the collector
scheme is pending Nathan's confirmation (plan §9).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(commands): Phase 2 — /plan + /implement delivery prepare-half

Delivery prepare-half (plan: docs/plans/agentic-loops-and-commands/README.md,
Phase 2).

- /plan — idea (incoming/<file>.md | #issue | inline) → grounded phased plan
  pack under docs/plans/<slug>/. Read-only on code: redirect-check →
  superpowers:brainstorming → Explore current-state map → research gate
  (/research + docs/findings/) → feature-dev:code-architect blueprint + reuse
  map → pack (README/00-current-state/NN-phase/decisions/progress-tracker).
  Untether exit-gate = "signature emitted by a real code path, not a fixture";
  coverage-inventory sweep dropped.
- /implement — execute ONE approved phase to a verified hand-off: gate-check →
  pre-flight ground-truth (#1200 analogue) → TDD in-scope → whole-class sweep
  (#1201 analogue) → verify (phase tests + pytest + ruff + rule-mandated suite
  on runner/schema/telegram surfaces) → commit to feature/<slug>-phase-N →
  STOP before the PR. Refuses multi-phase; never widens scope; defect work → /fix.
- docs/LOOPS.md: L1/L2 → available.

Live slash-command runs (/plan a small idea, /implement --dry-run) are operator
acceptance steps — project slash commands are operator-invoked, and /plan drives
brainstorming + Explore + code-architect subagents.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(commands): Phase 3 — /qa validate + /pr-dev + /pr-main release split

Delivery VALIDATE + RELEASE boundaries from the agentic-loops plan (P3):

- /qa — adaptive QA-0..QA-5; guide-first live-bot guardrails (default
  plan/dry-run, dev-bot allowlist + fail-closed, bounded + run-ID-tagged);
  SHA-bound attestation write on green via run-integration-tests.sh.
- /pr-dev — everyday finalise → ONE PR to dev with docs folded in
  (CHANGELOG/FAQ/## Tests), batch-cohesion, table-shaped body; may merge
  to dev only (--merge + confirm).
- /pr-main — stable bump + uv lock + changelog collapse + validate + FAQ
  → open dev→master PR, then STOP at Nathan's merge gate.

allowed-tools grant no forbidden verbs (no gh pr merge/tag/release/
fleet-rollout on /qa or /pr-main). Registry status flips ship in Phase 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(commands): Phase 4 — /handover, advisory reviewers, /docs, research findings

Optional/continuity half of the agentic-loops plan (P4) + registry completion:

- /handover — H0–H3 interruption stop-state, derived from persisted state
  (git/tests/quarantine, never chat memory), git-fingerprinted; defaults DOWN.
- .claude/agents/{debug,delivery,qa}-reviewer — advisory, non-authoring
  read-only reviewers (verdict + gaps; never edit/file/merge).
- /docs — thin escape-hatch to reconcile docs OUTSIDE a PR (default path
  stays folded into /pr-dev).
- docs/findings/README.md — the /research findings convention (reuses the
  global /research; provider/current-truth gate for /plan//debug//qa).

Registry: docs/LOOPS.md flips L3/L4a/L4b/L9 → available, adds the support-
commands + advisory-reviewers sections; workflow-commands.md adds /docs to
the authority ladder + the reviewer note.

Deliberately NOT built (plan gates behind "only if a real need appears"):
/context-health, /issue-triage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#675)

The fleet-rollout gate marker was effectively a reusable boolean — it bound
a version string but not the commit tested, so a marker written against
commit A satisfied a rollout of a different build at the same version.

- run-integration-tests.sh: record head_sha (auto-derived from the script's
  repo via git rev-parse, overridable via --head-sha / UT_INTEGRATION_HEAD_SHA)
  and machine-readable dev_bot_id (default 8678330610, UT_DEV_BOT_ID override)
  in the marker JSON. Existing fields unchanged (backward compatible).
- fleet-rollout.sh: surface the bound head_sha + dev-bot identity at the gate
  (Guardrail 3, informational) and warn on pre-#674 markers with no head_sha.
  No hard-gate change — old markers keep working.
- /qa (qa.md) Q-6: pass --head-sha as a first-class field instead of stuffing
  the SHA into --notes.
- tests/test_attestation_marker.py: 5 tests — auto-derive, both overrides,
  dev_bot_id default/override, tiers+notes preserved (temp-HOME subprocess).

Full suite: 3013 passed, 2 skipped, 83.03% coverage. Operator tooling only
(not shipped in the wheel) — no version bump, no changelog entry.

Closes #674

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…handoff-failure log (#668) (#676)

rc13 follow-ups to the v0.35.4 background/handoff batch.

#667 (#640 follow-up): capture proc_returncode in run_impl's finally, not
only the happy-path assignment after `await proc.wait()`. /cancel, /new,
drain, a task-group/JSONL-reader exception, and the early pipes RuntimeError
all skipped it and left proc_returncode None — so _is_signal_death(None) was
False and the auto-continue death-spiral guard was inert on exactly those
paths. manage_subprocess.__aexit__ already runs a shielded bounded
terminate+reap before the finally, so the capture is a guarded non-blocking
read (guarded because proc/stream can be unbound on early-exit paths).

#668 (#633 follow-up): bind engine/session_id/chat_id onto
session.handoff_check_failed so a persistent fail-closed probe failure is
attributable (the issue-watcher dedups by signature, collapsing every chat
into one report). Correct the stale serialize_session_owner comment in
settings.py to match the divert-fresh-WITHOUT-quarantine behaviour, noted as
a deliberate deviation from the #633 W4 design.

Tests: new cancel-mid-flight harness test driving a real ClaudeRunner
subprocess + hang_before_result fake-CLI scenario; attribution assertions on
test_633_probe_failure_fails_closed. Full suite green (3014 passed), ruff clean.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…skUserQuestion (#598) (#677)

A keyboard-clear edit that the outbox coalesces out before dispatch — a
newer same-key progress edit supersedes it, a delete/replace drops it, or
a RetryAfter requeue collides — was resolved to None, indistinguishable at
the edit() layer from a real editMessageText failure. That produced the
`transport.edit.failed error=None` warning after every answered
AskUserQuestion, and is why PR #608's HTTP-layer error capture never
populated it: a superseded op never makes an HTTP call, so no api_error is
recorded.

Edit ops now opt into a per-op SUPERSEDED outbox disposition threaded
through all three intentional-drop sites (same-key supersede, drop_pending,
RetryAfter requeue collision). TelegramTransport.edit treats SUPERSEDED as a
benign transport.edit.superseded no-op and returns the ref. send/delete keep
the None failure contract, so a superseded replace-send can never be misread
as a sent message.

Root-caused with gpt-5.6-sol (pal). 5 new tests across test_telegram_queue.py
and test_telegram_bridge.py. Bumps staging to 0.35.4rc14.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cs (#678)

Bump 0.35.4rc14 → 0.35.4 (pyproject.toml + uv.lock) and finalise the v0.35.4
release documentation ahead of the dev→master release PR.

- CHANGELOG: finalise the v0.35.4 section (rc1–rc14), add the #674 attestation-
  marker entry, refresh the header comment + date; regenerate the docs mirror
  (docs/reference/changelog.md gains the missing v0.35.3 + v0.35.4 sections)
- config reference: document 8 new-in-0.35.4 TOML keys — pre_result_silence_timeout,
  post_result_limbo_grace, post_result_bg_max_hold (#591/#592/#647),
  session_handoff_bg_timeout_s (#647), voice_transcription_url_allowlist + SSRF
  note (#381), outbox_deliver_directories/outbox_notify_skipped (#524/#628),
  allow_unauthenticated_webhooks (#382)
- how-to/update: new "Upgrading to v0.35.4" operator section
- voice-notes + telegram ref: SSRF caveat + allowlist for local Whisper (#381)
- triggers + webhooks-and-cron: auth="none" non-loopback refusal (#382)
- file-transfer + faq: outbox directory-zip delivery now shipped (#628)
- troubleshooting + faq: empty-resume quarantine-and-fresh (#631/#632),
  queued-follow-up notice (#654)
- README: retire "progressive cooldown" → plan outline gate (#570); outbox zip
- CLAUDE.md test count 3015 → 3021; specification + tutorial version strings

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 139 files, which is 39 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9eb33a9a-c08a-4281-b518-f2b97313f5c7

📥 Commits

Reviewing files that changed from the base of the PR and between 4009531 and db40a9c.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (139)
  • .claude/agents/debug-reviewer.md
  • .claude/agents/delivery-reviewer.md
  • .claude/agents/qa-reviewer.md
  • .claude/commands/debug.md
  • .claude/commands/debug/README.md
  • .claude/commands/debug/output-template.md
  • .claude/commands/debug/step-classify.md
  • .claude/commands/debug/step-evidence.md
  • .claude/commands/debug/step-fix.md
  • .claude/commands/debug/step-research.md
  • .claude/commands/debug/step-verify.md
  • .claude/commands/debug/systemic-patterns.md
  • .claude/commands/docs.md
  • .claude/commands/fix.md
  • .claude/commands/handover.md
  • .claude/commands/implement.md
  • .claude/commands/kaizen-review.md
  • .claude/commands/kaizen.md
  • .claude/commands/plan.md
  • .claude/commands/pr-dev.md
  • .claude/commands/pr-main.md
  • .claude/commands/qa.md
  • .claude/hooks/kaizen-stop-nudge.sh
  • .claude/hooks/tests/test-kaizen-stop-nudge.sh
  • .claude/rules/control-channel.md
  • .claude/rules/dev-workflow.md
  • .claude/rules/kaizen.md
  • .claude/rules/release-discipline.md
  • .claude/rules/runner-development.md
  • .claude/rules/workflow-commands.md
  • .claude/skills/claude-stream-json/SKILL.md
  • .claude/skills/release-coordination/SKILL.md
  • .claude/skills/untether-architecture/SKILL.md
  • .github/workflows/auto-tag-on-master.yml
  • .github/workflows/ci.yml
  • .github/workflows/codeql.yml
  • .github/workflows/prerelease-deps.yml
  • .github/workflows/release.yml
  • .gitignore
  • CHANGELOG.md
  • CLAUDE.md
  • README.md
  • contrib/README-issue-watcher.md
  • contrib/untether.service
  • docs/LOOPS.md
  • docs/faq/faq.md
  • docs/findings/README.md
  • docs/how-to/file-transfer.md
  • docs/how-to/plan-mode.md
  • docs/how-to/troubleshooting.md
  • docs/how-to/update.md
  • docs/how-to/voice-notes.md
  • docs/how-to/webhooks-and-cron.md
  • docs/kaizen/README.md
  • docs/plans/2026-07-16-noop-resume-remediation/01-rc7-recovery-and-quarantine.md
  • docs/plans/2026-07-16-noop-resume-remediation/02-rc7-background-terminal-detection.md
  • docs/plans/2026-07-16-noop-resume-remediation/03-rc8-serialization-and-lifecycle.md
  • docs/plans/2026-07-16-noop-resume-remediation/04-test-strategy.md
  • docs/plans/2026-07-16-noop-resume-remediation/05-upstream-tracking.md
  • docs/plans/2026-07-16-noop-resume-remediation/README.md
  • docs/reference/changelog.md
  • docs/reference/config.md
  • docs/reference/dev-instance.md
  • docs/reference/fleet-ping-verification.md
  • docs/reference/integration-testing.md
  • docs/reference/runners/claude/runner.md
  • docs/reference/runners/claude/stream-json-cheatsheet.md
  • docs/reference/runners/pi/untether-events.md
  • docs/reference/specification.md
  • docs/reference/transports/telegram.md
  • docs/reference/triggers/triggers.md
  • docs/tutorials/first-run.md
  • docs/tutorials/install.md
  • docs/tutorials/interactive-control.md
  • pyproject.toml
  • scripts/audit-noop-resume.sh
  • scripts/fleet-rollback.sh
  • scripts/fleet-rollout.sh
  • scripts/fleet-status.sh
  • scripts/run-integration-tests.sh
  • src/untether/lockfile.py
  • src/untether/runner.py
  • src/untether/runner_bridge.py
  • src/untether/runners/amp.py
  • src/untether/runners/claude.py
  • src/untether/runners/codex.py
  • src/untether/runners/gemini.py
  • src/untether/runners/opencode.py
  • src/untether/runners/pi.py
  • src/untether/schemas/claude.py
  • src/untether/session_quarantine.py
  • src/untether/settings.py
  • src/untether/shutdown.py
  • src/untether/telegram/backend.py
  • src/untether/telegram/bridge.py
  • src/untether/telegram/client.py
  • src/untether/telegram/client_api.py
  • src/untether/telegram/commands/claude_control.py
  • src/untether/telegram/commands/config.py
  • src/untether/telegram/commands/restart.py
  • src/untether/telegram/engine_overrides.py
  • src/untether/telegram/loop.py
  • src/untether/telegram/outbox.py
  • src/untether/telegram/outbox_delivery.py
  • src/untether/telegram/voice.py
  • src/untether/triggers/manager.py
  • src/untether/triggers/server.py
  • src/untether/triggers/settings.py
  • src/untether/triggers/ssrf.py
  • src/untether/utils/proc_diag.py
  • src/untether/utils/subprocess.py
  • tests/conftest.py
  • tests/fake_clis/fake_claude_noop_resume.py
  • tests/test_ask_user_question.py
  • tests/test_attestation_marker.py
  • tests/test_bridge_config_reload.py
  • tests/test_claude_control.py
  • tests/test_claude_runner.py
  • tests/test_claude_schema.py
  • tests/test_cooldown_bypass.py
  • tests/test_exec_bridge.py
  • tests/test_exec_runner.py
  • tests/test_lockfile.py
  • tests/test_loop_coverage.py
  • tests/test_noop_resume_harness.py
  • tests/test_outbox_delivery.py
  • tests/test_pi_runner.py
  • tests/test_proc_diag.py
  • tests/test_session_quarantine.py
  • tests/test_settings.py
  • tests/test_shutdown.py
  • tests/test_subprocess.py
  • tests/test_telegram_bridge.py
  • tests/test_telegram_client_api.py
  • tests/test_telegram_engine_overrides.py
  • tests/test_telegram_queue.py
  • tests/test_telegram_voice.py
  • tests/test_trigger_manager.py
  • tests/test_trigger_server.py

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

socket-security Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedaiohttp@​3.13.5 ⏵ 3.14.197 +1100 +21100100100
Updatedpydantic-settings@​2.12.0 ⏵ 2.14.2100 +1100 +2100100100

View full report

…e-A auto-retry, voice SSRF) (#681)

Aligns user-facing docs with features already shipped and changelogged in
v0.35.4. No behaviour change; no CHANGELOG entry (these describe #570/#572/#381,
already covered in the v0.35.4 release notes).

- README.md: "Plan mode ... progressive cooldown" -> "the outline gate"
  (the progressive cooldown was retired in v0.35.4, #570)
- docs/how-to/troubleshooting.md: the Type-A stall note said auto-retry was
  "deferred to a future release" — it shipped in v0.35.4 (#572). Now documents
  the opt-in `[watchdog] stream_idle_auto_retry` / `stream_idle_max_retries`.
- docs/how-to/troubleshooting.md: voice section now notes the SSRF refusal of
  private/loopback `voice_transcription_base_url` and the
  `voice_transcription_url_allowlist` opt-in (#381).
- docs/tutorials/interactive-control.md: two spots described the retired
  progressive cooldown as current behaviour — rewritten to the outline gate,
  matching docs/how-to/plan-mode.md.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nathanschram
Nathan Schram (nathanschram) merged commit 4285dad into master Jul 23, 2026
37 checks passed
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.

1 participant