Skip to content

fix(hooks): deny PR-activity subscribe, allow unsubscribe without a prompt - #497

Merged
wenzowski merged 1 commit into
mainfrom
claude/cloud-624-refinement-ouwa4w
Aug 19, 2026
Merged

fix(hooks): deny PR-activity subscribe, allow unsubscribe without a prompt#497
wenzowski merged 1 commit into
mainfrom
claude/cloud-624-refinement-ouwa4w

Conversation

@wenzowski

@wenzowski wenzowski commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

DO-NOT-CLOSE CLOUD-518

Two permission rules in .claude/settings.json. Nothing else changes.

Why

AGENTS.md forbids babysitting a PR, and land now refuses to land one whose
webhook subscription is still armed. But the harness arms a subscription on
every PR this repo opens, and dropping it required a human approval on every
single PR — so the enforced path was also the prompting path. A gate whose
remedy needs a person each time is a gate that gets worked around rather than
satisfied, which is exactly what happened on the PR before this one.

What

  • deny subscribe_pr_activity — the arming this repo does not want is
    refused rather than approved by reflex.
  • allow unsubscribe_pr_activity — dropping one is always available with no
    prompt. Nothing is risked by allowing it: unsubscribing only ever narrows what
    reaches the session.

Both are spelled for the GitHub and the Claude Code Remote servers. The same
connector is exposed under more than one name, and a rule matching one spelling
gates nothing — the measured instability behind CLOUD-178, and the same reason
issue-read-guard's matcher is suffix-anchored.

Why this does not complete CLOUD-518

That issue has two halves. This is the subscription half. The other half — the
<wake reason="external-event"> envelopes rendering to the human as raw XML —
is untouched, so the marker above declines the close rather than moving the
board on a half-done issue.

It also does not close CLOUD-673: a task still cannot authenticate to the
session's own MCP endpoint, so land cannot drop a subscription on the agent's
behalf. The drop stays a tool call the agent makes and records with
mise run pr-unsubscribed record. What changes is that the call no longer
stops for a person.

Verification

  • jq -e '.permissions.deny | index("mcp__github__subscribe_pr_activity")' and
    the matching allow index both resolve — valid JSON, rules present.
  • Observed live: after the edit, mcp__github__subscribe_pr_activity became
    unavailable in this session, so the deny is in force rather than merely
    written.
  • hk gate green on commit, including mcp-allow-check ("every allow rule names
    a tool it can match, and every enabled server has a grant"), rules-drift,
    hooks-wiring-check, hook-pin-check and mcp-timeout-budget.

Summary by CodeRabbit

  • Chores
    • Updated development tool permissions for pull request activity management.
    • Enabled unsubscribing from pull request activity while restricting new subscription actions.

…rompt

AGENTS.md forbids babysitting a PR, and `land` now refuses to land one whose
webhook subscription is still armed (CLOUD-518's gate). But the harness arms a
subscription on every PR this repo opens, and dropping it needed a human
approval on every single PR — so the enforced path was also the prompting path,
which is how a gate gets worked around instead of satisfied.

Two permission rules make the posture the default rather than a per-PR
negotiation:

- deny `subscribe_pr_activity`, so the arming this repo does not want is
  refused rather than approved by reflex.
- allow `unsubscribe_pr_activity`, so dropping one is always available without
  a prompt. Nothing is lost by allowing it: unsubscribing only ever narrows
  what reaches the session.

Both are spelled for the GitHub and Claude Code Remote servers, because the
same connector is exposed under more than one name and a rule matching only
one spelling gates nothing (CLOUD-178's measured instability, the same reason
`issue-read-guard`'s matcher is suffix-anchored).

This does not close CLOUD-673 — a task still cannot authenticate to the
session's own MCP endpoint, so `land` cannot unsubscribe on the agent's
behalf; the drop stays a tool call the agent makes and records with
`mise run pr-unsubscribed record`.

Refs: CLOUD-518, CLOUD-651
@linear-code

linear-code Bot commented Aug 19, 2026

Copy link
Copy Markdown
CLOUD-518 Every PR this repo opens is auto-subscribed to webhook wakes, against AGENTS.md's own ban — and the envelopes render to the human as raw XML

Why

AGENTS.md forbids PR-webhook babysitting twice — land drives the loop "no timeout, no cap, never the PR webhook", and "No heartbeats … to babysit a PR — fetching CI on demand is fine, the ban is on timers." The harness does it anyway, and the repo has no mechanism on it.

Two halves, measured 2026-08-13.

1. The subscription arms itself. The remote-execution harness prompt carries a standing instruction — "After creating a PR in a session, immediately call subscribe_pr_activity … Don't ask first" — which directly contradicts the repo contract. Worse, it is not only the agent: PR #402 was auto-subscribed without any subscribe_pr_activity call being made, ~30 s after creation. So the earlier assumption that unsubscribing is a durable fix is wrong; it has to be repeated after every PR, by hand, forever.

PR #397  05:56:19  subscription.created  (agent called subscribe)
PR #397  05:57:54  subscription.created  (nobody called it)
PR #402  07:07:24  subscription.created  (nobody called it)

2. The envelopes render to the human as raw XML. Reported by the repo owner mid-session: pages of <wake reason="external-event">…</wake> in the chat transcript. Traced in ~/.claude/projects/<project>/<session>.jsonl — every injected prompt is stored as a queued_command attachment carrying two provenance fields, and the wake path fills them in transposed:

injection origin.kind commandMode count
<task-notification> null task-notification 4
<wake reason="external-event"> task-notification prompt 5

commandMode is what the renderer keys on, and prompt is the value meaning a human typed this. So the client displays a webhook envelope exactly as it displays the user's own messages — verbatim, unstyled, as a chat bubble. Task notifications travel the same queue all session and are never shown, which is why this looked like it "suddenly started".

Hooks were suspected and are excluded, tested rather than argued. Exactly four UserPromptSubmit hook_success records exist in the session, matching the four human prompts; zero for the nine machine injections. Hooks fire on dequeue, and the wake envelopes were enqueued then removed in one batch without ever being dequeued. The commandMode label is stamped 1–4 ms before its own enqueue record, upstream of the queue and of anything a hook could reach.

Why it is worth a row rather than a shrug. The rendering half is Anthropic's and cannot be fixed here. The subscription half is ours and is a live contract violation: every PR this repo opens starts a webhook stream that AGENTS.md says must not exist, and the only remedy today is an agent remembering to call unsubscribe_pr_activity — prose, therefore feedforward only, therefore non-negotiable 2's shape exactly. It has already been missed once and caught twice by hand in a single session.

Refinement — Ready

  • Source of truth (§1). GitHub's subscription state for a PR, readable through the same MCP surface that sets it. Not the harness prompt, which is not ours to edit.
  • Mechanism as a computable predicate (§2). After land opens or readies a PR: is this session subscribed to it? A land lap already talks to the PR, so the check has a natural home there and costs no extra round trip. Unsubscribe is idempotent, so the remedy is safe to apply unconditionally rather than gated on a read.
  • Effect (§3). A GitHub write, but only ever the removal of a subscription this repo's contract says should not exist.
  • Output & exit contract (§5). Pointer-only: the PR number and that a subscription was dropped. Never the event bodies.
  • Commit / bump (§6). fix(land) — patch until 0.1.0.
  • Test obligation (§7). A subscribed PR is unsubscribed and says so; an unsubscribed PR is a silent no-op; a failing GitHub call costs the lap nothing and never ends a landing. Mutation-checked: removing the unsubscribe must redden the first row.
  • Blockers (§8). None.

Acceptance

  • A PR opened by land is not left subscribed, and nothing depends on an agent remembering.
  • The repo's ban on webhook babysitting has a mechanism rather than only prose.
  • The rendering defect is reported upstream with the transcript evidence above, and this issue records the report — it is explicitly not fixable here.

Not in this issue

Whether webhook-driven work is ever right. AGENTS.md says no for this repo and that is not relitigated here; this only closes the gap between the rule and its enforcement.

CLOUD-673 A task cannot authenticate to the session's own MCP endpoint, so CLOUD-518's unsubscribe is unreachable from `land`

Why

CLOUD-518 needs land to call unsubscribe_pr_activity, a tool that exists only on the session's toolbox MCP server. A comment on that issue measured the surface as reachable from a bash task and concluded credentials "ride the agent proxy". That half is wrong, re-measured 2026-08-18 on the CLOUD-518 branch.

request result
POST …/v2/ccr-sessions/<session>/mcp (toolbox), X-Session-UUID + X-MCP-Server-ID from the injected config 401 authentication required
same, forced through --proxy $HTTPS_PROXY 401 authentication required
POST …/v2/ccr-sessions/<session>/github/mcp, same two headers 401 authentication required

The mechanism is visible in the environment rather than inferred: no_proxy carries anthropic.com,.anthropic.com,*.anthropic.com, so a request to that host bypasses the agent proxy entirely and nothing injects a credential on the way out. Forcing the proxy does not help — it holds no Anthropic API credential for that host. The two headers are routing identifiers, not authorization; the client authenticates with something the injected config does not contain.

What survives from the CLOUD-518 measurement: the endpoint shape, the header names, that the surface is an http MCP endpoint, and that a JSON-RPC tools/call is the right request. What does not: that a task can make one succeed.

Searching the container for the client's credential is deliberately not the fix. A task scraping the client's secrets would be worse than the defect it closes, even where it works.

What this costs right now

PR #490 carries a complete, tested, correctly-failing implementation of CLOUD-518's mechanism, and on its own landing attempt it printed, twice per lap:

land: could NOT drop #490's webhook subscription (http 401) — the landing is unaffected

So CLOUD-518's first acceptance criterion — "a PR opened by land is not left subscribed" — is not met, and cannot be met from a task until this is answered. #490 is held as a draft with DO-NOT-CLOSE for that reason.

Refinement — Ready

  • Source of truth (§1). Whether a JSON-RPC tools/call issued by a task, using only what a task legitimately has, is accepted by the session's MCP endpoint. Observable as an HTTP status.
  • Mechanism as a computable predicate (§2). drop_pr_subscription in mise-tasks/land already is the predicate — it POSTs and reports the status. This issue supplies the missing input: a credential or local surface a task may present. Candidates, none verified: a session-scoped token deliberately exposed to tasks; a local socket the client already authenticates over; a first-party CLI subcommand that proxies one tool call. If any exists, only the header assembly in that function changes.
  • Effect (§3). None on its own. It unblocks a write that only ever removes a subscription.
  • Output & exit contract (§5). Pointer-only, unchanged from CLOUD-518: the PR number and whether a subscription was dropped. A credential must never be printed, logged, or committed.
  • Test obligation (§7). The ten rows in tests/land.bats cover the shape against a stubbed curl and need no change. What this adds is the one thing no suite can assert — a single end-to-end observation that a real subscription was removed — recorded here as a measurement, since a suite cannot hold a per-session credential.
  • Blockers (§8). None. This blocks CLOUD-518.

Acceptance

  • Either a task-presentable credential (or equivalent local surface) is identified and drop_pr_subscription fires end to end, evidenced by one real subscription.created being removed;
  • or it is established that none exists — in which case CLOUD-518 cannot be closed by a mechanism in this repo, and fix(land): drop the webhook subscription the harness arms on every PR #490's scaffold should be reconsidered rather than left in the tree looking like coverage.

Not in this issue

CLOUD-624 Evaluate token-normalized phrase matching for hook/log predicates

Why. A small class of predicates has to read assistant prose, and each one matches raw substrings. The failures witnessed so far are surface-form brittleness rather than policy error: the policy holds and the string does not.

The two that carry weight are witnessed — a real turn is cited for each, on its own issue. one thing to flag … is silent where one thing I would flag … fires (CLOUD-487, whose body cites the turn); worth naming was silent until CLOUD-387 landed one verb set across both openers. Neither was found by enumeration — each was found by noticing that a sibling string fired, which is the defect this issue is about.

What this issue has NOT got is a measurement, and the shape of that gap decides the work. Re-running the shipped task against those two sentences on 2026-08-18 reproduces both verdicts, and reproducing a cited miss is worth exactly one thing: confirming it is still live before building on it. It is not evidence about any proposed matcher. A pattern authored against known strings hits them by construction — the same circularity that makes the hand-authored literal set the defect in the first place. Any sentence typed at the task to see whether it fires is an invention, not a witness, and stop-posture-check's own rule already refuses those (worth calling out stays out as "unwitnessed, and not already in this file").

The question. Should Batten add a deterministic parsing/normalization pass for the prose half of these predicates — a token-aware pipeline built on tokenizers or any equivalent local tokenizer/parser — so a rule matches over a normalized token stream or a small token-pattern grammar instead of raw substrings?

This is not a request for learned NLP, embeddings, or model judgement. The line from CLOUD-95 stays: bounded, deterministic, structural, explainable. The prose side may nominate a candidate; the verdict must still come from a typed join.

One constraint is settled and is not re-litigated by the evaluation. Deterministic segmentation, lemmatization and finite-state morphology estimate nothing, so they are admissible; naming one crate does not make the category learned. A token pattern over named classes is more explainable than a regex alternation — these tokens matched this rule is inspectable, while the edges of a hand-tuned alternation are not — which is the direction non-negotiable rule 3 points. Dependency count is a cost to price, never a verdict.

What to evaluate

Four blocks. Each carries the questions its measurement has to answer.

1. Normalization and token-pattern matching. Sentence segmentation, token boundaries, punctuation normalization, quote/code/fence stripping, inflection-safe matching. Compare today's literal alternations against token-aware patterns that express small equivalence classes — one thing [I would|to] flag, worth [noting|mentioning|naming] — capturing witnessed inflection and boundary variants without widening to nearby but semantically different phrasing. The output must stay attributable as: these tokens matched this rule.

  • Is a token-normalized matcher materially more reliable than today's literal sets on the witnessed misses above?
  • Can it improve recall without reopening the false-positive classes CLOUD-252 and CLOUD-323 already dropped?
  • What is the smallest deterministic representation that buys the robustness: token boundaries only, normalization plus stemming, or a tiny grammar over token classes?

2. Corpus-derived candidate generation. Mine the durable turn/message corpus rather than hand-authoring a phrase list. An n-gram hit may nominate a candidate rule shape; the typed join still decides.

  • Are bigrams and trigrams mined from a real corpus a better source of candidate shapes than hand-authored literal expansion?
  • Do within-message or within-turn co-occurrence and token-pair correlation find reliable punt/deferral language that adjacency alone misses?
  • Which window is the unit — assistant message, full turn, or hook-evaluation span? Too small is noisy; too large washes out the local structure the predicate depends on. The verdict names the scope that preserved the most signal.
  • Does correlation over labeled positives and negatives outperform raw n-gram frequency once very common background tokens are discounted?
  • Which Batten-specific low-signal tokens can be filtered the way generic stop-words are, without dropping load-bearing words? Domain-measured, never imported wholesale from a generic English list.
  • Do opener / verb / destination roles become clearer as token positions than as a raw string? Test on one thing [I would|to] flag, worth [noting|mentioning|naming], awaiting review, left as, we can decide later.
  • A phrase-family graph over highly correlated token pairs around flag, worth, parked, review, later and filed is exploratory only: it may reveal local language families before any candidate is proposed for a gate.
  • Bigram and trigram counts are sparse by nature. Record explicitly whether the available corpus is large enough to justify shipping anything derived from it.

3. Negation. not, no, never and without are load-bearing and must survive any low-signal filtering, since each can reverse or neutralize a nearby candidate phrase.

  • Can token-normalized matching separate not filed, not verified, not landed, never checked and without a ticket from superficially similar but non-actionable phrasing? The goal is avoiding false positives and false negatives caused by local negation, not generic sentiment inversion.
  • Should the negator set extend to contractions — can't, don't, won't — or do those widen the search space faster than they help recall on modern assistant prose?

4. Substrate and join. Keep CLOUD-326's boundary: prose narrows candidates, typed facts decide. Evaluate against the existing join shapes — candidate punt / deferral phrasing, no durable write in the same turn, no owning issue named in the relevant scope.

  • Should this live in the shell guards as a preprocessing step, or move directly into the engine path implied by CLOUD-95 and CLOUD-312?
  • What host artifact is the input: the current session transcript, a durable hook log, or both behind one typed interface? Prefer durable hook/log records over ephemeral session-only transcripts wherever the host can provide them, so the substrate works over a stable logged turn/message artifact rather than assuming raw transcripts persist.
  • How is the matcher measured and reported so every shipped pattern stays audited rather than accreting by intuition?

The matcher half is not split off, and the reason is a predicate

The matcher half looks separable: the witnessed misses exist today, so a token pattern could be authored and shown to fire with no corpus at all. It is not separable, because a matcher authored against known strings hits those strings by construction — recall over them measures nothing, which is the same circularity that makes a hand-authored literal set the defect this issue exists to fix.

What decides the matcher question is the false-positive class a widened pattern opens, and CLOUD-252 and CLOUD-323 are where broader shapes actually died. That measurement needs negative examples in the register the predicate reads, and this repository's durable artifacts are not that register. The one corpus run anyone has done here is the 2026-08-17 session's, recorded in mem:prior-art-and-issue-hygiene and not re-run since: the shipped set plus two candidate expansions fired 0 times over 60 merged PR bodies and 100 issue/PR comments, and 7 times over 400 commit messages with 0 true positives — because this repo's own issue hygiene prescribes a register in which the phrasing barely appears. An uninformative sample is not a clean bill, so it cannot license a widening either.

A second reason that null result settles nothing, and it is independent of the register. The predicate is a conjunction — the phrasing AND no durable write in the same turn. A durable artifact contains no instance of "nothing durable was written", by construction, so a zero on that conjunct is guaranteed before anyone runs anything and discovers exactly nothing. The literal's own false-positive rate does not depend on that conjunct at all, which is why the run tells you nothing about the half it was supposed to test. Where a measurement's construction guarantees its own zero, the finding is about the measurement. Both halves therefore wait on the same corpus, and splitting would buy an unmeasurable verdict. mem:prior-art-and-issue-hygiene also carries the two ways this was previously argued wrong.

Existing cases this should be measured against

  • Hedged framing, currently shipped but recall-limited
  • Transcript-backed finding-shape matching
    • CLOUD-252
    • The measured result there is the caution: broader shapes were dropped when they were noisy or dead.
  • PR-body deferral matching and exemption logic
    • CLOUD-323
    • CLOUD-338
    • These show both sides of the problem: literal choice and structural exemption logic.
  • Corpus / substrate constraints

Non-goals

  • No model-based judgement, embeddings, semantic similarity, or hidden scoring.
  • No free-text payload output; pointer-only still holds.
  • No prose-only predicate that decides by itself. A token match without the typed side of the join is advisory at best.

Acceptance

  • At least one candidate deterministic parsing pipeline is described concretely enough to implement.
  • It is evaluated against the witnessed misses above and the unshipped deferral shape in CLOUD-326.
  • The false-positive side is reported as counts over a named corpus, with true positives among the firings, so nothing ships on an uninformative sample.
  • The evaluation states whether corpus-derived n-grams, co-occurrence and correlation add real signal beyond token normalization alone.
  • It states which context window and which negation set produced the strongest measured results.
  • It states clearly whether token normalization is enough, or whether the remaining failures are really corpus / join / ownership problems rather than matching problems.
  • If the answer is yes, it identifies the first predicate to pilot and where it should live, filed as its own issue.
  • If the answer is no, the rejection is explicit, grounded in Batten's measured false-positive and corpus constraints, and says what evidence would change it.

Related


Refinement — Ready (a matcher verdict measured on the witnessed misses, with the typed join still the decider)

Refinement gate: Definition of Ready & Done. Evaluate-and-decide spike: §3 (no new command) and §4 (nothing generated) do not apply. §2 is discharged by naming the pilot predicate a positive verdict authorizes, not by shipping one.

  • Source of truth (§1). The literal sets inside the predicates that read prose today — HEDGES in mise-tasks/stop-posture-check and DEFERRALS in mise-tasks/deferral-check — are the artifact under evaluation. The policy is out of scope: this spike changes how a candidate phrase is recognised, never which posture is a violation.
  • Lands as (§1). The verdict and its measurements recorded on this issue, plus one follow-on issue for the pilot if the answer is yes. The committable consequence is a memory edit — the measured result and the rejected shapes belong in mem:prior-art-and-issue-hygiene, which is what gives this issue a PR and therefore a reachable In Review; a verdict recorded only on the tracker lands nothing and cannot satisfy the board's own predicate. No docs/ path is created (non-negotiable rule 7).
  • Computable predicate (§2). The verdict is checkable against the Acceptance list above, and its floor is the two witnessed misses above, which mise-tasks/stop-posture-check reproduces on demand: a pipeline that does not fire on the infinitive hedge has not been exercised. Clearing that floor is a precondition, never the verdict — the verdict is the false-positive measurement, which is what the blocker gates. A positive verdict names one pilot predicate, filed as its own issue and expressed as a rule row, with the typed join still deciding — a token match without the typed side is advisory at best.
  • Output & exit (§5). Pointer-only holds hardest over prose: a finding reports the rule id, path:line and a count, never the matched sentence. The 0/1/2/3 table is unchanged, 2 as the policy verdict.
  • Commit / bump (§6). docsno bump — the deliverable is a recorded decision, so nothing releases.
  • Test obligation (§7). Not applicable while the deliverable is a verdict; the pilot issue carries the behavioural test, and its floor is one case per witnessed miss above, so a recall claim is asserted by a test rather than by prose. A case invented to exercise a pattern is not a witnessed miss and does not count toward that floor.
  • Blockers (§8). blockedBy CLOUD-651 — the collector is what makes a corpus exist, and every measurement this issue owes is over one: the discovery half by construction, and the matcher half through the false-positive measurement that decides it. That issue is now refined and in Todo, and its own destination — batten-corpus, CLOUD-671 — is the frontier this chain starts at, so this is two hops from implementable and refinable now either way. relatedTo CLOUD-388 rather than blocked by it: it supplies the reading, not the corpus, and its deliverable has landed — mise run transcript-corpus-check reported transcript-corpus independent=1 min=2, exit 1, on 2026-08-18 — and the one it counted is this session's own transcript, the only .jsonl under the host root, so the independent count is zero.

Verdict, 2026-08-18: reject, pending corpus

Not a rejection on the merits. The matcher question is real, and the substrate objections raised against it were withdrawn on evidence — tokenizers being BPE does not make the category learned, and the witnessed misses are genuine. This is a rejection for undecidability: the half that decides the question cannot be measured here, and this issue's own Acceptance already contemplates the outcome ("If the answer is no, the rejection is explicit, grounded in Batten's measured false-positive and corpus constraints, and says what evidence would change it.")

The grounds are three measurements this issue already carries.

  1. Recall against the witnessed misses is not a measurement. A pattern authored against known strings hits them by construction — the same circularity that makes a hand-authored literal set the defect this issue exists to fix. Reproducing both cited misses on 2026-08-18 confirms they are still live; it is evidence about nothing else.
  2. The false-positive side is what decides it, and its register has no corpus. mise run transcript-corpus-check answers independent=1 min=2, exit 1 — and the 1 is the asking session's own transcript, so the independent count is zero.
  3. The one corpus run that exists cannot bear the weight. 2026-08-17, over this repo's durable artifacts: 0 firings across 60 PR bodies and 100 issue/PR comments, 7 across 400 commit messages with 0 true positives. Wrong register, and its zero on the no-durable-write conjunct is guaranteed by construction — a durable artifact contains no instance of "nothing durable was written".

Why now rather than waiting. CLOUD-671 was rescoped on 2026-08-18 into batten serve, a first-class capability, and states plainly that it "is not a means of unblocking CLOUD-624 — those spikes are downstream consumers that happen to need a corpus; the corpus does not derive its scope from them." Once the blocker becomes a capability with its own schedule, the dependent stops being blocked and becomes a consumer. It has no claim on that schedule, so waiting buys nothing and a blockedBy edge asserts a relationship that no longer exists. The edge to CLOUD-651 is dropped here and carried by the successor, which is where it belongs.

The re-open predicate. A command and an exit code, not a judgement, and grounded in this repo's own prior measurements rather than an invented number:

Re-open when the corpus holds more assistant turns than the largest measurement any shipped prose literal already rests on — the 113-turn transcript behind finding-sink-check's table. Below that, a new verdict would rest on thinner evidence than the gates it is trying to improve.

Checkable today only in part, stated as such rather than dressed up: transcript-corpus-check counts independent sessions, not turns. So the predicate lands in two pieces — the session count via the existing gate, and the turn count named as the substantive condition. Making that a runnable command is the successor's own §2 obligation; no turn-counting gate is invented here.

Successor. CLOUD-677, on the Session corpus — full-fidelity capture milestone: the matcher comparison and the corpus-derived discovery half, both measured against a populated corpus, blockedBy CLOUD-651. This issue stays on Phase 2 — moving it would be the roster cheat CLOUD-632's acceptance names.

Lands as. The §1 Lands as clause above is what makes this verdict landable rather than tracker-only: the measured result, and the two arguments withdrawn on evidence, go into mem:prior-art-and-issue-hygiene. That edit is the PR, and the PR is what makes In Review reachable.

Review in Linear

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4e0f00b3-e0b1-4a43-8465-6a1621559fe4

📥 Commits

Reviewing files that changed from the base of the PR and between 86e7f8c and d671184.

📒 Files selected for processing (1)
  • .claude/settings.json

Included review availability: Your plan provides up to 3 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The Claude settings update allows GitHub and Claude Remote PR activity unsubscription tools. It denies the corresponding subscription tools.

Changes

Claude PR activity permissions

Layer / File(s) Summary
Update PR activity permission rules
.claude/settings.json
The settings allow GitHub and Claude Remote PR activity unsubscription operations and deny the corresponding subscription operations.

Estimated code review effort: 1 (Trivial) | ~2 minutes

Merge Risk: ⚪ Minimal · up to d6711

This PR narrows PR-activity permissions by denying new subscriptions while allowing unsubscriptions without prompting. The change is localized and no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

  • button-inc/batten#488: Updates the same Claude Remote PR activity permission rules and corrects Remote server identifiers.

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main permission changes: deny PR-activity subscriptions and allow unsubscriptions without prompts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/cloud-624-refinement-ouwa4w

Warning

Review ran into problems

🔥 Problems

These MCP integrations need to be re-authenticated in the Integrations settings: Linear


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

@sonarqubecloud

Copy link
Copy Markdown

@wenzowski
wenzowski marked this pull request as ready for review August 19, 2026 00:36
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski
wenzowski merged commit d671184 into main Aug 19, 2026
18 checks passed
@wenzowski
wenzowski deleted the claude/cloud-624-refinement-ouwa4w branch August 19, 2026 00:49
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