Skip to content

Omnibus: resolve 29 triaged issues (27 closed, 4 partially) - #143

Merged
retog merged 33 commits into
mainfrom
claude/issues-triage-prioritize-pklgiz
Aug 22, 2026
Merged

Omnibus: resolve 29 triaged issues (27 closed, 4 partially)#143
retog merged 33 commits into
mainfrom
claude/issues-triage-prioritize-pklgiz

Conversation

@retog

@retog retog commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Resolves the 29 dispatchable tasks from the full triage of all 46 open issues (every issue was re-verified against main @ e2f4e22 before implementation; the remaining 17 issues need maintainer decisions or owner-side settings and are untouched). 32 commits, one per issue (a small series for the CalDAV feature); each commit message names its issue.

Bug fixes

Feature

  • CalDAV gateway: write calendar events with optional /sends verification #13 — CalDAV gateway: a new caldav-gateway compose service creates calendar events on any RFC 4791 server via the generic caldav library (no provider-specific code), gated by CALDAV_SEND_POLICY with the same allow/trust/verify semantics and fail-safe default as the messenger gateways; pending events are approved through the existing /sends flow (enrolled via MESSENGER_GATEWAYS, which needed no changes); caldav-push.py mirrors signal-push.py; standalone send-policy test included.

Documentation accuracy

Testing

  • Full standalone suite passes: 29/29 scripts (24 pre-existing + 5 new: attachments, agent sender, stripped-headers surfacing, transcript-cleanup model, CalDAV send policy; test_notification_settings.py and test_email_strip_headers.py extended in place). test_push_notify.py was not runnable in the build sandbox (pywebpush wheel build fails there); it is unmodified and covered by CI.
  • py_compile on every changed Python file, bash -n on both changed shell scripts, YAML validation of docker-compose.yml.
  • Behavioral fixes were exercised directly: process-group kill verified against a detached grandchild, ingest-sensors against a fixture chamber, git-serialize global-option parsing against the issue's own case table.

Notes for review

Fixes #2, fixes #3, fixes #5, fixes #9, fixes #13, fixes #26, fixes #27, fixes #29, fixes #31, fixes #32, fixes #33, fixes #35, fixes #36, fixes #37, fixes #38, fixes #39, fixes #40, fixes #54, fixes #61, fixes #65, fixes #69, fixes #74, fixes #75, fixes #87, fixes #92, fixes #112, fixes #124.
Addresses parts of #23, #28, #46, #67 (kept open).

🤖 Generated with Claude Code

https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ


Generated by Claude Code

claude added 30 commits August 22, 2026 17:21
The default REPO_ROOT silently fell back to the framework checkout, which
has no observations/ directory, so the documented invocation ingested
nothing and exited 0. Require --chamber/CHAMBER_DIR and validate it holds
observations/, exiting nonzero with a clear message otherwise. Also adds
the documented "Pushes" Garmin column to GARMIN_COLUMNS and fixes the
Ultrahuman observation-count divisor (10 -> 5, matching the other sites).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
#75)

os.environ.get(name, default) only applies its default when the key is
absent, so RETINUE_TRIAGE_MODEL="" expanded to "" instead of the declared
${VAR:-default}. Match real shell :- semantics: fall back to the default
whenever the variable is unset or empty.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
…#37)

The wrapper keyed serialization on $1 alone, so `git -C path commit` (the
form the web gateway's own auto-commit uses) had $1 == "-C" and fell
through unserialized. Parse and forward git's global options (-C, -c,
--git-dir, ...) before selecting the subcommand, so both the lock-key
rev-parse and the real invocation see them.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
subprocess.run(..., timeout=...) only ever signals the direct child, so a
job that spawned its own children left them running past the recorded
timeout -- the scheduler stopped waiting, not the job. Spawn jobs with
start_new_session=True and, on timeout, escalate SIGTERM then (after a
grace period) SIGKILL against the whole process group via os.killpg.

Retry/backoff for a failed or timed-out job and /status polling are
deferred to a follow-up.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
_slug() defaulted the empty id to the literal "default" before sanitising
and folded any of "/", ":", etc. onto "_", so distinct ids like "" and
"default", or "anthropic/claude-opus-4" and "anthropic:claude-opus-4",
collapsed onto the same generated node. Percent-encode the id instead
(urllib.parse.quote, no fallback) so distinct ids always stay distinct.

Also documents, alongside the sibling frontmatter-watcher caveat, that
chambers/_generated/ indexing depends on the still-open qlever-dir#10 fix.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
)

A project explicitly parked with paused: true (recurring-projects.py's
resting state) was swept and re-spawned an agent session anyway, unlike
the dashboard's own project query which already filters paused out.
Mirrors the existing FILTER NOT EXISTS style used for `resolved`.

The resolved predicate's own handling (issue #23, part 1) is left as-is,
pending a maintainer decision on the canonical completion predicate.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
_store_attachments() wrote every file as a bare uuid4 hex, so a session
reading a PDF back got mojibake instead of a rendered document (images
were fine -- they're content-sniffed). Derive a suffix from the filename
or content type, same as the sibling _store_message_files(), and keep
the bare-hex "id" field (used as the download URL's path segment)
unchanged; both readers of the stored path fall back to no suffix for
attachments recorded before this fix.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
An agent-opened dashboard thread reported success even with zero
subscribed devices, indistinguishable from an actually-delivered
escalation. _push_conv_notification() now returns the subscriber
count (logging when it's zero); the two /internal/conversations POST
handlers surface it as push_subscribers in their JSON response, and
conversation-push.py warns on stderr when it comes back 0.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
_conv_add_message()/_new_conv() already accepted an optional `agent`
override -- the displayed sender name (e.g. "Coach") for a relay
answering on a subagent's behalf -- and the frontend already rendered
it, but no caller ever set it. Add --agent to conversation-push.py
(both the thread-open and --thread append paths) and thread it through
the two /internal/conversations POST handlers into storage.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
Four deferred follow-ups from #60's Zoho/Exchange workaround (item 3,
the replace-vs-extend semantics, stays deferred):

- approve_pending_send()'s return value was discarded at its only call
  site, so the one place that could report the workaround firing never
  did; log the stripped header names on approval.
- The "override or extend via SEND_STRIP_HEADERS" comment claimed
  extend semantics the code never had (it replaces the default list).
- The bounce comment and its matching test docstring named the wrong
  exchangelib exception (InvalidCharsetException); PR #60's own NDRs
  say ExchangeDataException.
- Document SEND_STRIP_HEADERS in .env.example, next to EMAIL_SEND_POLICY.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
…E_MODEL (#29)

The dashboard's voice-input cleanup pass hard-defaulted to "haiku"
regardless of RETINUE_CLAUDE_MODEL, so an Ollama/OpenRouter deployment's
cleanup pass silently asked its gateway for an Anthropic-only model
name -- the one Claude Code process that didn't honor the selected
backend. An explicit TRANSCRIPT_CLEANUP_MODEL still wins; only the
default changes. Also document TRANSCRIPT_CLEANUP/_MODEL in
.env.example and qualify README's "every Claude Code process" claim
to name this path.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
…#39)

Move signal-gateway's SIGNAL_PENDING_SENDS_DIR default off /tmp onto the
signal-data volume so the approval queue survives container recreation
(the documented update path), not just a restart. Separately, stop
qlever-static from caching its gzip decompression by mere existence —
decompress fresh only when actually building, and free the copy
afterwards — so the documented reindex recipe (now up -d
--force-recreate in all three places it's written) picks up changed
source instead of the stale cached file.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
_forward_to_inbox passed the raw group_id as the news source for a
Signal group, unlike the Telegram/WhatsApp gateways which pass a
readable name. Resolve the name from the existing groups roster
(_list_groups(), also exposed at GET /groups), falling back to the id
on a miss.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
…#74)

The legacy per-entry "slug" field stopped being read once slugs were
derived from the base_url's hostname, but a deployment that still sets
it got no diagnostic — just a gateway silently keyed under a different
slug than before. Print a warning at discovery time instead.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
signal-push.py and whatsapp-push.py described SIGNAL_SEND_POLICY /
WHATSAPP_SEND_POLICY as a property of the recipient, the opposite of
how the gateways actually resolve it (the gateway's own account).
signal-push.py also had no policy explanation at all. Telegram's
"bots" wording is left to #9, which already covers it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
)

The Telegram gateway logs in as the user's own Telegram account
(Telethon/MTProto), not a bot, which is a much larger surface (reads
the user's own DMs, messages any contact as them) than the README's
opening sentence implied — undermining the later argument for why
TELEGRAM_SEND_POLICY fails safe to verify. Fixes the same "bot"
wording in telegram-push.py's docstring/help and in a stale test
comment; CLAUDE.md already had this right.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
The approval endpoint checks only that a send is pending, not who calls
it, so this was a false absolute in nine public places. Replaced each
with the calibrated, already-accurate description used by
telegram-gateway.py: a verify send is registered as pending and
transmitted only after approval on the web gateway's /sends page.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
Only messaging/mailbox credentials are kept out of the agent's context;
a repo token and model-gateway keys are not. Restored the "messaging"
qualifier in comparison.md, fixed review.md's stale entrypoint.sh
anchor, and qualified SECURITY.md's "cannot steal credentials / cannot
silently send" bullet to match what the sidecars and send policies
actually guarantee.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
CI now runs the test suite on every push and PR, so "zero test
coverage and no CI" and the now-done CI recommendation were both
stale; replaced with the narrower, still-true gap (no test constructs
the gateway's request handler, so endpoint authorization itself is
untested) and dropped raw line/file counts that keep going stale.
Re-pinned the two dead relative citations as permalinks at e2f4e22.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
)

passTLSClientCert never strips a forged cert header — it only adds one
when a real certificate was presented. The actual protection is
Traefik's entrypoint forwardedHeaders default, which the security note
and gateway_auth.py's SECURITY comment now credit correctly, with the
operator caveats (never set forwardedHeaders.insecure; trustedIPs
delegates stripping to that proxy). Separately, the base compose file
ships no Traefik labels at all — the mTLS middlewares live only in
docker-compose.override.example.yml — so the wiring section now says
to copy that file and keep its three load-bearing labels instead of
claiming the wiring is already done.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
…#38)

marvin.md and dolores.md told themselves they were confined to "this
chamber," but the tools: restriction only limits which tools a
subagent has, not which paths those tools may open — every mounted
chamber is readable, exactly as SECURITY.md's known-limitations list
already says. Reworded both to state the real, enforced boundary
(Read/Glob/Grep only) and point at that limitation instead of
asserting one that isn't there.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
#31)

The spawn command hardcoded --permission-mode dontAsk, so it was the
one claude invocation CLAUDE_PERMISSION_MODE didn't reach; it now
reads the same variable the entrypoint/scheduler/web-gateway do,
falling back to dontAsk. Also replaced the "the security boundary is
the allowlist, not the permission-mode" line — the shipped allowlist
(Bash(*), Write(**), Edit(**)) restricts nothing — with what actually
contains a spawned session: the container, the credential sidecars,
and the send policies.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
#69)

Follow-up to #68: forward now routes through _dispatch_message (the
same choke point as send/reply) and renders an HTML-only source body
as plain text, but the skill never said so. Noted both next to the
forward example and under the send-control table.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
Replace remaining references to ~15 s rebuild latency with accurate
"tens of seconds" phrasing and clarify RDF-only trigger per PR #55.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
…#33)

The plugin cache is keyed by the source repo's commit at install time,
not by a version field that most shipped manifests do not declare.
Update both CLAUDE.md and sync-plugins.py's docstring accordingly.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
Update CLAUDE.md and comparison.md to document that the dashboard's
four data cards are commented out in index.html until a refresh job
regenerates them, mirroring webapp/README.md's accurate description.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
…arify URLs (#5)

Add documentation for GARMIN_EMAIL and GARMIN_PASSWORD, with a warning
to use app passwords. Give CONVERSATION_BASE_URL a proper definition
and scope SEND_APPROVAL_BASE_URL to e-mail approvals only (messenger
gateways use SEND_APPROVAL_BASE_URL from their own sections). Remove
duplicate ANTHROPIC_API_KEY definition from line 215.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
…entified (#32)

Replace the origin-based if/else (which silently falls through to the
nested-layout path when git can't read the gitdir, e.g. an unmounted
submodule .git) with content-based detection over two candidate paths
that errors out instead of guessing. Documents the resulting third
mount layout and its /tmp-clone fallback, both in the section prose
and the repos table.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
Write-only gateway that creates calendar events via a generic RFC 4791
client library (no provider-specific code paths) and gates writes with
an allow/trust/verify policy mirroring the messenger gateways, with
pending events approvable through the existing /pending-sends contract.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
Mirrors signal-push.py: posts an event to the gateway's /create-event
endpoint and, when the write is queued for approval, prints the
pending-approval notice with the resolved /sends URL.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
claude added 3 commits August 22, 2026 17:21
Adds the caldav-gateway service to docker-compose.yml, documents its
CALDAV_* variables in .env.example, and shows how to enrol it onto
/sends via the existing MESSENGER_GATEWAYS mechanism (already
channel-agnostic, so no other framework code needed to change) in the
override example and the README.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
Mirrors tests/test_signal_send_policy.py: policy-category resolution,
the pending-event store lifecycle (approve/reject/unknown/malformed
id), approval-slug derivation, and event-datetime validation — all
runnable without the caldav package or network access.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
…able (#39)

The pending-sends dir moved off /tmp onto the signal-data volume, which made
the import-time mkdir fail for non-root importers (the CI test runner). Create
it best-effort with a warning instead, and retry before each pending-send
write; all read sites already tolerate a missing directory.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014oMZgYcHBqdbRPV8J9oWuZ
@retog
retog merged commit 8d64e9d into main Aug 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment