Omnibus: resolve 29 triaged issues (27 closed, 4 partially) - #143
Merged
Conversation
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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
_store_message_files), so a session can actually open a forwarded PDF; old extensionless files still serve.ingest-sensors.pynow requires an explicit chamber root and fails loudly instead of silently ingesting nothing; adds the missing Garmin "Pushes" column; fixes the Ultrahuman count divisor (10 → 5).${VAR:-default}falls back on set-but-empty values, matching shell semantics.git -C <repo> …, the form the web gateway's own auto-commit uses #37 —git-serialize.shparses git's global options (-C,-c,--git-dir, …) before dispatching, so the web gateway'sgit -C …auto-commits are serialized too, with the lock keyed to the right repository./tmponto the persistedsignal-datavolume;qlever-staticno longer reindexes from a stale decompression cache on the documented refresh path (docs updated to match).conversation-push.pywarns when a thread was opened with zero subscribed devices._generatedindexing caveat todocs/triple-stores.md. The entrypoint-ordering question stays open in the issue.RETINUE_CLAUDE_MODELis documented as reaching every Claude Code process; the dashboard transcript cleanup hard-codeshaiku#29 — dashboard transcript cleanup falls back toRETINUE_CLAUDE_MODELbefore the built-in default;TRANSCRIPT_CLEANUP*documented in.env.example; README's "reaches every Claude Code process" claim qualified.agentsender-name override (PR #86 follow-up) #92 / PR #86 follow-up: the new "agent" sender-override is documented and read by the frontend, but no call site ever sets it #87 — the documentedagentsender-name override is finally settable:conversation-push.py --agent NAMEflows through both internal endpoints to the already-rendering frontend. (PR #86 follow-up: the new "agent" sender-override is documented and read by the frontend, but no call site ever sets it #87 is the duplicate of Dashboard: wire up theagentsender-name override (PR #86 follow-up) #92.)group_id, with the id as fallback.slugkey in aMESSENGER_GATEWAYSentry now logs a diagnostic instead of being silently ignored.SEND_STRIP_HEADERScomment no longer claims "extend"; the wrong exchangelib exception name is corrected in comment and test;SEND_STRIP_HEADERSdocumented in.env.example. Item 3 (replace-vs-extend semantics) is deferred to the issue./statusreporting stay open in the issue.resolved: trueescape hatch emits no triple, so a project can never leave the sweep #23 (partial) — the agent-self-review sweep also skipspaused: trueprojects. The canonical resolved-predicate question stays open in the issue.spawn-sessioncalls the settings allowlist a security boundary, and hard-codes a permission mode the deployment cannot configure #31 —spawn-sessionhonorsCLAUDE_PERMISSION_MODE(hardcoded mode becomes the fallback) and no longer calls the settings allowlist a security boundary.Feature
caldav-gatewaycompose service creates calendar events on any RFC 4791 server via the genericcaldavlibrary (no provider-specific code), gated byCALDAV_SEND_POLICYwith the sameallow/trust/verifysemantics and fail-safe default as the messenger gateways; pending events are approved through the existing/sendsflow (enrolled viaMESSENGER_GATEWAYS, which needed no changes);caldav-push.pymirrorssignal-push.py; standalone send-policy test included.Documentation accuracy
comparison.mdfixed.comparison.md/SECURITY.md; stalereview.mdanchor fixed.review.mdrefreshed: test/CI claims match reality, recommendation docs: reindex latency is stated as ~15 s (measured 15-20 s) and the README omits converter extensions #2 marked done, dead citations re-pinned as permalinks.gateway_auth.py's comment) credits entrypointforwardedHeaders, notpassTLSClientCert, for spoofed-header stripping; the false "labels already wired" claim replaced.SECURITY.mdsays doesn't exist.use-email-clientskill documentsforward's send-control gate and HTML-body handling.plugin.json's version — and no shipped manifest declares one #33 — plugin-cache staleness correctly attributed to install-time-commit keying, notplugin.jsonversions..env.example: Garmin credentials documented,CONVERSATION_BASE_URLdefined, duplicate keys reconciled.Testing
test_notification_settings.pyandtest_email_strip_headers.pyextended in place).test_push_notify.pywas not runnable in the build sandbox (pywebpush wheel build fails there); it is unmodified and covered by CI.py_compileon every changed Python file,bash -non both changed shell scripts, YAML validation ofdocker-compose.yml.ingest-sensorsagainst a fixture chamber,git-serializeglobal-option parsing against the issue's own case table.Notes for review
signal-gateway.py, which open PR feat(gateways): store inbound media as HTTP reference, not inline RDF #127 also modifies — whichever merges second needs a trivial rebase.resolved: trueescape hatch emits no triple, so a project can never leave the sweep #23, Boot emitter (PR #22): docs state the _generated indexing as unconditional, and the model slug is not injective #28, Outcomes recorded into fields nothing reads: the updater's result is unreachable from both callers, and the scheduler's job status is written and never consulted #46, PR #60 follow-up: five small defects deferred at merge (header report path, stale doc, override/extend mismatch, wrong exception string, missing .env.example entry) #67) intentionally stay open: each has a deferred half awaiting a maintainer decision, as does the rest of the triage's decision queue (Agent can self-approve averify-policy send, bypassing human approval #19, Secrets leak into agent subsessions via inherited env; unset can't fix it — pass via stdin, not env_file #15, The updater cannot survive updating itself, and re-derives a compose invocation only the deployment knows — a submodule-layout override is invisible to it #90, LiteLLM is documented as optional but is an unconditional startup dependency of the retinue container, and its database needs a variable .env.example ships commented out #11, Chambers declared withpathare invisible to the life store, and four docs say the opposite #30, Projects card returns no rows: web-gateway query and md2ttl.py converter disagree on namespace, predicate and actor URI shape #1, Personalized news agent: broadcast ingestion + time-decaying relevance feed #25, Dashboard: agent-offered choice buttons in conversation threads #18, .github/copilot-instructions.md is scoped to VS Code sessions, and the only Copilot mode that has written to this repo is the coding agent — which it tells not to push, and never points at CONTRIBUTING.md #34, README's service list names 4 of 12 compose services, and the Layout tree predates six directories #10, tell_ara should close the loop: reconcile a reported outcome with project state #79, Declarative chamber inboxes: Archivist as ingestion orchestrator #135, Epic: messenger reactions & message-scoped replies (Signal / WhatsApp / Telegram) #130, feat: integrate SMS Gateway private server into Retinue #16, and owner-action check-signal-cli pushes a bump branch but cannot open the PR: Actions is blocked from creating pull requests #4).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