# How nodeterm Suppresses Grok's Routine Per-Tool Permission Prompts

> Learn how nodeterm suppresses Grok's permission prompts by normalizing payloads and handling idle notifications efficiently. Enhance your workflow today.

- Repository: [eneskirca/nodeterm](https://github.com/eneskirca/nodeterm)
- Tags: how-to-guide
- Published: 2026-08-23

---

**TLDR: nodeterm suppresses Grok's routine per-tool permission prompts by canonicalising Grok's camel-case hook payloads, denylisting the `permissionprompt` notification type in [`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts), and routing Grok's idle notifications through the same permission-mode flag handling used by Claude.**

According to the `eneskirca/nodeterm` source code, Grok is treated as a built-in agent with a dedicated normalization path. When Grok emits a notification that would normally trigger a "permission required" UI prompt, nodeterm classifies it as a routine idle message rather than a blocked-agent event. This keeps the interface clean and avoids unnecessary user interruptions.

## How nodeterm's Grok Permission Prompt Suppression Works

The suppression mechanic is implemented in three coordinated layers across [`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts) and [`src/shared/agents/approval-mode.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/approval-mode.ts). Each layer handles a distinct part of the problem: identifying the agent, filtering the notification type, and ensuring permission-mode flags don't accidentally re-trigger the blocked state.

### 1. Canonicalisation of Grok's Hook Payload

Grok's hook payload uses camel-case keys that differ from every other agent supported by nodeterm. To compare payloads reliably, [`normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/normalize.ts) defines two helper functions:

```ts
const grokCanonical = (v?: string) => (v ?? '')
  .toLowerCase()
  .replace(/[^a-z]/g, '');

const grokEventName = (p: GrokPayload) =>
  grokCanonical(p.hookEventName ?? p.hook_event_name);

const notificationType = grokCanonical(p.notificationType ?? p.notification_type ?? p.type);

```

`grokCanonical` lower-cases the input and strips every non-alphabetic character, producing a stable identifier. `grokEventName` and `notificationType` then use that canonical form to recognize the event name and notification type regardless of whether Grok sent camel-case or snake-case keys.

### 2. Filtering Routine Notifications with a Denylist

Inside `grokRawFields`, after extracting the `event` and `type`, nodeterm checks the canonicalised type against a denylist. The value `permissionprompt` — which is Grok's specific wording for a per-tool permission request — is recognized and **silently ignored**:

```ts
// The asking types are a CLOSED set; grok's routine permission_prompt is excluded.
if (type === 'permissionprompt') {
  // Suppress the prompt – treat as idle.
  return null;
}

```

Returning `null` prevents the generic "blocked" badge from appearing on the node and stops the UI from prompting the user. Because the routine prompt is filtered at this stage, it never becomes a blocked-agent event.

### 3. Unified Permission-Mode Handling

Grok shares the same flag syntax as Claude (`--permission-mode`). The generic permission-mode utilities in [`src/shared/agents/approval-mode.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/approval-mode.ts) — `hasPermissionMode` and `withPermissionMode` — explicitly include Grok in their capability list:

```ts
const PERMISSION_MODE_CAPABLE = ['claude', 'grok'];

```

When a Grok node is created, `withPermissionMode('grok', …)` appends the appropriate flag only for modes other than the default `manual`. Since the routine prompt is already filtered in the normalization step, Grok never reaches the "blocked" state even if the user has selected a non-default mode.

## Practical Code Examples

### Creating a Grok node with the correct permission flag

```ts
import { createAgentNode } from 'src/renderer/state/workspace';
import { withPermissionMode } from 'src/shared/agents/approval-mode';

// Example: start a Grok session with "auto" mode (the default for Grok)
const cmd = withPermissionMode('grok', 'grok', 'auto'); // -> "grok --permission-mode auto"
createAgentNode('grok', { initialCommand: cmd });

```

### Normalizing a Grok hook payload

```ts
import { grokRawFields } from 'src/shared/agents/normalize';

const payload = {
  hookEventName: 'preToolUse',
  notificationType: 'permissionPrompt',
  // ...other fields
};

const result = grokRawFields(payload);
// result is null because the notification type is the routine permission prompt.

```

### Suppressing the UI badge in the status reducer

```ts
// In the agent-status store reducer
if (normalized === null) {
  // Grok routine prompt => ignore, keep current state.
  return state;
}

```

Because the normalization step returns `null`, the reducer keeps the node in its current state and never marks it as blocked.

## Key Files in the nodeterm Repository

| File | Purpose |
|------|---------|
| [`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts) | Core Grok payload parsing and suppression logic |
| [`src/shared/agents/approval-mode.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/approval-mode.ts) | Permission-mode utilities that include Grok |
| [`src/shared/agents/normalize.grok.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.grok.test.ts) | Test suite confirming the suppression behavior |
| [`src/shared/keybindings.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/keybindings.ts) | Registration of the "New Grok node" command |

These files together implement the detection, canonicalisation, and filtering that keep Grok's routine per-tool permission prompts from surfacing in the Nodeterm UI.

## Summary

- **Grok prompts are suppressed at the normalization layer**: `grokRawFields` returns `null` for the `permissionprompt` notification type, preventing any blocked state.
- **Canonicalisation handles Grok's unique key format**: `grokCanonical` lower-cases and strips non-alphabetic characters so event names and notification types can be compared reliably.
- **Permission-mode handling is unified with Claude**: `withPermissionMode` includes Grok, but because the routine prompt never reaches the reducer, the wrong flag never produces a blocked badge.
- **The behavior is covered by tests**: [`normalize.grok.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/normalize.grok.test.ts) verifies the suppression works as expected.

## Frequently Asked Questions

### Why does Grok use `permissionPrompt` instead of `permission_prompt`?

Grok's hook payload uses camel-case keys, which differ from the snake-case style used by most other agents. nodeterm's `grokCanonical` normalizes both formats into a single lower-case identifier, so `permissionPrompt`, `permission_prompt`, and `permissionprompt` are all detected as the same routine event.

### Does nodeterm suppress all Grok permission prompts, not just routine ones?

No — the suppression only targets the routine `permissionprompt` notification, which is the notification Grok sends on every ordinary tool use. If Grok emits a genuinely blocking event (such as a different event type not in the denylist), it will still be processed normally and can still trigger a blocked state in the UI.

### How does forcing a non-default permission-mode affect Grok's prompts?

The `withPermissionMode` helper adds the `--permission-mode` flag to the Grok command for modes other than the default `manual`. However, because the routine `permissionprompt` notification is already filtered at the normalization layer, that flag never translates into a visible "blocked" badge in the interface.

### Where in the source code can I find the denylist logic?

The denylist check lives inside `grokRawFields` in [`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts). The exact line compares the canonicalised notification type against the string `permissionprompt` and returns `null` to suppress the prompt.