NanoClaw

NanoClaw binds pending approvals to the instance that delivered them

NanoClaw proposes recording the delivering instance on pending approvals, failing closed when stored sign-in cannot be verified, and declining Slack invites without an owner. The cluster narrows who may complete setup and approval work.

← Back to homeOriginal source ↗

nanocoai/nanoclaw PR #3340 is the inspected primary source: “fix(approvals): record the delivering instance on pending_approvals.” The related records below were inspected as supporting context rather than independent confirmation.

The facts

- nanocoai/nanoclaw PR #3340 was created 2026-08-18T23:03:46Z and was open at inspection; its title is “fix(approvals): record the delivering instance on pending_approvals.” - nanocoai/nanoclaw PR #3339 was created 2026-08-18T23:03:44Z and was open at inspection; its title is “fix(setup): fail closed when a stored sign-in cannot be verified.” - nanocoai/nanoclaw PR #3342 was created 2026-08-18T23:03:50Z and was open at inspection; its title is “feat(slack): decline owner-absent channel invites instead of carding them.” - The repository reported 30546 stars, 913 open issues, default branch main, and last push 2026-08-18T23:14:38Z. - The latest tagged-release baseline checked was v2.2.0 published 2026-08-13T20:43:27Z.

What changed

nanocoai/nanoclaw PR #3340 reports: What pending_approvals gains an instance column, so OneCLI credential cards are posted and edited by the same bot identity that owns the DM. Why Delivery dispatch resolves adapters exact-key, with no channelType fallback — deliberately, so a named instance can never send through a sibling bot's token: The OneCLI approval path passed no instance on either call. On an install whose bots are all named adapter instances, with nothing registered under the bare channelType key: **The card post** throws MissingChannelAdapterError, which is caught and turned into an auto-deny — a credentialed request refused for a reason that has nothing to do with the request. **The expiry edit** throws too, and that throw was swallowed into a log.warn. The pending row is deleted regardless, so the card keeps showing live Approve/Reject buttons that resolve nothing. The edit path is not only a timeout concern: sweepStaleApprovals runs it on **every host boot**, so each restart orphaned every in-flight card. This is reachable today because ensureUserDm resolves through getChannelAdapter, which *does* fall back across instances of a channel type. It can therefore hand back a DM owned by a named instance, which exact-key dispatch then cannot address. How Migration 023** (023-approvals-instance.ts) adds a nullable instance column — an ALTER TABLE ADD COLUMN, following the shape of migration 018 (approver_user_id). 023 is the next

nanocoai/nanoclaw PR #3339 reports: The bug A NanoClaw account credential that cannot be checked is treated as one that passed. setup/registry-login.ts probes the stored credential for idempotence, so a resumed setup does not drag anyone back through a browser dance it already finished. When that probe comes back unreachable, the driver keeps the credential and exits 0. For the image pull that is the right call — a cached image needs no round trip, so a transient outage costs nothing. For a caller that is about to *spend* the token it is not, and the exit code is the only thing a caller sees. Two details make this reachable rather than theoretical: probeSession returns unreachable for every status that is not 200/401/403. A gateway 404, a proxy error, and a credential the service no longer knows all land there. The comment above bearerGet already says as much. It is the only outcome a stale credential can reach without being caught. The existing.api!== api check above it cannot fire on a record written before that field existed, because readAccountCredential fills the gap by resolving the base the same way this run does — so the two always compare equal whenever no --api overrides them, which is every wizard run. The visible result, with NANOCLAW_SLACK_AGENTS=1: the Slack pre-step spends an unchecked token, the service returns

nanocoai/nanoclaw PR #3342 reports: What Owner-absent Slack **channel** invites are now declined in place rather than escalated to the owner as an approve/reject card. Why Slack lets any workspace member add an installed app to a channel. The bot appears in the member list the moment they do — visibly present before its owner has agreed to anything. The authorization boundary already held: slackChannelCardInterceptor returned 'card', nothing was wired, nothing was routed, and the owner got a registration card. So this was never an access bypass. The gap was expectation: Everyone in the channel sees a bot that looks like it is listening. The only person told otherwise was the owner. The owner is asked to adjudicate an invitation they did not make, in a channel they may not be in. The existing stranger-DM policy already answers the same question the other way — decline, notify, wire nothing. Channels now match it. How In the owner-absent branch of slackChannelCardInterceptor (src/modules/slack-room-membership/membership.ts), a **channel** now gets: 1. **One line in the channel**, posted as the invited bot through its own instance: it can only be connected by its owner, please remove it, the owner can add it back to connect it. Needs only chat:write. 2. **An informational FYI DM to the owner** — plain text, no buttons — naming the channel and whoever invited the bot.

Why it matters

An approval detached from its delivery instance can cross runtime boundaries. Unverified stored identity and ownerless invites widen the same authority gap. The operator test is whether the system remains bounded and its receipts still describe the action after failure, retry or restart.

Current

The primary record was open when captured on 2026-08-19. The tagged-release baseline was v2.2.0 published 2026-08-13T20:43:27Z. Repository metadata, full source bodies, current pull requests and issues, releases, Google News, Hacker News, Lobsters, Metamesh and the rendered ClawCharts row were inspected. ClawCharts selected the subject; it did not prove the claim.

Evidence

The primary URL is https://github.com/nanocoai/nanoclaw/pull/3340. Supporting records are https://github.com/nanocoai/nanoclaw/pull/3339 and https://github.com/nanocoai/nanoclaw/pull/3342. Source bodies, timestamps and states are preserved in the daily evidence bundle. Test counts and reproductions remain attributed to their authors unless identified as independently rerun.

Source boundary

Open work is described as open, closed work as closed, and operator reports as reports. A pull request is evidence of proposed or reviewed direction, not proof of a shipped release. Search residue, package mirrors and historically published source spines were excluded.

Operator take

Bind approval completion to the delivering instance, revalidate stored identity before use, and reject channel enrollment when no accountable owner exists. Preserve a before-state receipt, make the smallest reversible change, and verify the original failure independently.

Caveat

Public project records are mutable. Status, scope and evidence can change after publication. This brief records the inspected state and does not authorize changes to a reader’s deployment.

Source inspected; source state, environment and release boundary remain explicit.