Skip to content

HITL: "Always allow this tool" via Glean ToolPreferences (set_tool_approval gateway meta-tool)#42

Draft
pragati-agrawal-glean wants to merge 5 commits into
mainfrom
pragati/hitl-scoped-approvals
Draft

HITL: "Always allow this tool" via Glean ToolPreferences (set_tool_approval gateway meta-tool)#42
pragati-agrawal-glean wants to merge 5 commits into
mainfrom
pragati/hitl-scoped-approvals

Conversation

@pragati-agrawal-glean

@pragati-agrawal-glean pragati-agrawal-glean commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Description

HITL "Always allow this tool" for run_tool, persisted to Glean the way Glean Assistant does.

The approval prompt shows a single inline checkbox above the built-in Accept/Decline:

Action: <tool>
  ...args...
  Always allow this tool: ☐
   Accept   Decline
  • Accept unticked → allow once · Accept ticked → always allow · Decline → not executed (fail-closed).
  • boolean field because Claude Code renders that as an inline checkbox (a string enum renders as a collapsed accordion — verified from the CC binary).

Persistence — via Glean's shared ToolPreferences (not local, not REST)

Ticking "always allow" calls the new set_tool_approval MCP-gateway meta-tool (companion: askscio/scio#265738), which writes the user's preference to the same ToolPreferences store Assistant uses (surface ASSISTANT). It's read back through find_skills' existing requires_approval (already honored by the gate), so approvals unify with Assistant and need no plugin-side storage. A process-local session set gives immediate effect before the next find_skills refresh.

Why not local/REST: the plugin's dynamic-registration token holds only the MCP scope; POST /api/v1/{save,list}usersettings and /toolpreferences need internal:web_api (empirically 401). The gateway meta-tool is the only reachable write path — hence the scio companion PR.

Changes

  • src/tools/run-tool.ts — single-checkbox schema; on "always", best-effort callRemoteTool("set_tool_approval", {server_id, tool_name, value:"ALWAYS_ALLOWED"}) + session set; gate short-circuits on the session set. A failed persist never breaks execution.
  • Deleted src/remote-approvals.ts, src/approval-keys.ts, src/tool-permissions-store.ts (+ tests) — the earlier REST/local-file design (superseded).
  • src/index.ts — dropped the tool-permissions reset.

Testing

  • npm run typecheck clean · npm test195 passed · npm run build clean.

Rollout

Until scio#265738 deploys, the set_tool_approval call errors and is swallowed (execution unaffected); reads still honor any Assistant-set approvals via find_skills.

🤖 Generated with Claude Code

pragati-agrawal-glean and others added 2 commits July 22, 2026 12:10
Replace the run_tool HITL prompt's empty elicitation schema with a single
enum picker offering four scopes:

  - Allow for this task    -> approve this one call (default)
  - Allow in this session  -> remember for the session (session-allow-store)
  - Allow across sessions  -> remember always (tool-permissions-store)
  - Decline                -> not executed

Before prompting, run_tool now skips the gate when the tool is already
granted for the session or across sessions (per-tool key), alongside the
existing bypassPermissions skip. "always" persists to a local ~/.glean file
using the same flat key shape as Glean's user-settings store
(pluginToolApprovals.<tool> = "true") behind a swappable seam, so it can move
to the backend settings API once the plugin can reach it.

setup({reset}) clears the persistent store. Fail-closed behavior is
unchanged: any non-accept action, an explicit "decline" choice, or a
timeout/error never executes the action.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Simplify the scoped-approval prompt to match the Glean Assistant model:
Accept = allow once, or tick "Always allow this tool" to persist. Drop the
per-session tier — it can't be represented on a remote MCP server (no
client-local session state) and shouldn't diverge from Assistant.

- Elicitation schema is now a single boolean field, which Claude Code renders
  as an inline checkbox shown upfront, instead of a string enum (which CC
  renders as a collapsed ▶ accordion that hides the choices).
- Remove src/session-allow-store.ts and all session wiring.
- Message spells out: "Accept = allow once. Tick the box to always allow".
- "always" still persists via tool-permissions-store using the Glean
  user-settings key shape (pluginToolApprovals.<tool>) behind a swappable seam.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pragati-agrawal-glean pragati-agrawal-glean changed the title Scoped HITL approvals: task / session / always / decline HITL: "Always allow this tool" checkbox (allow once vs always) Jul 23, 2026
pragati-agrawal-glean and others added 2 commits July 23, 2026 17:27
The single "Always allow this tool" checkbox + Accept/Decline is
self-explanatory; the extra hint line was clutter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add GLEAN_REMOTE_TOOL_APPROVALS (default off). When on, "always allow this
tool" upserts to Glean's per-user settings (saveusersettings/listusersettings,
key pluginToolApprovals.<tool>) AND the local file; reads prefer the remote
value and fall back to the local file when the remote call fails
(unscoped/offline). Off = today's local-only behavior.

- New src/remote-approvals.ts: best-effort REST transport (bearer from
  loadCredentials, origin from resolveServerUrl); never throws.
- New src/approval-keys.ts: shared key namespace (avoids a store<->remote cycle).
- tool-permissions-store.ts: async + flag dispatch + in-process read cache.
- run-tool.ts / index.ts: await the now-async store calls.
- Tests: remote-approvals (injected fetch) + store dispatch (mocked remote):
  remote-preferred reads, local fallback on failure, dual write, no-throw.
- README: document the flag + the internal:web_api scope caveat.

Scope caveat: the plugin's MCP-scoped token can't hit saveusersettings
(needs internal:web_api) — flag ships off; local fallback keeps grants working.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pragati-agrawal-glean pragati-agrawal-glean changed the title HITL: "Always allow this tool" checkbox (allow once vs always) HITL: "Always allow this tool" + flag-protected remote (Glean) persistence Jul 23, 2026
@pragati-agrawal-glean pragati-agrawal-glean changed the title HITL: "Always allow this tool" + flag-protected remote (Glean) persistence HITL: "Always allow this tool" via Glean ToolPreferences (set_tool_approval gateway meta-tool) Jul 24, 2026
…oval gateway meta-tool

Replace the REST/local-file persistence (which 401s: the plugin's MCP-scope token
can't call /api/v1/saveusersettings) with a call to a new set_tool_approval
MCP-gateway meta-tool, so "always allow" lands in the same ToolPreferences store
Glean Assistant uses (surface ASSISTANT). Reads already come back via find_skills'
requires_approval (honored by the gate); a process-local session set gives
immediate effect before the next find_skills refresh.

- run-tool.ts: on "always", best-effort callRemoteTool("set_tool_approval",
  {server_id, tool_name, value:"ALWAYS_ALLOWED"}) + sessionApproved set; gate
  short-circuits on the session set. No persisted local store.
- Delete remote-approvals.ts, approval-keys.ts, tool-permissions-store.ts (+ tests).
- index.ts: drop the tool-permissions reset.
- Depends on the scio set_tool_approval meta-tool (separate PR) to actually
  persist; until deployed the call errors and is swallowed (execution unaffected).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pragati-agrawal-glean
pragati-agrawal-glean force-pushed the pragati/hitl-scoped-approvals branch from 208f83f to dad0d59 Compare July 24, 2026 07:56
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