# How Nodeterm Handles Gemini’s ToolPermission Notifications

> Learn how Nodeterm manages Gemini ToolPermission notifications by blocking agent states and pausing execution until user approval via the UI.

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

---

**Nodeterm maps Gemini’s ToolPermission notifications to a blocked agent state, pausing execution until the user explicitly grants approval through the UI.**

When building terminal-based AI agent interfaces, handling permission prompts gracefully is critical for both security and user experience. In the `eneskirca/nodeterm` repository, the developers implemented a robust normalization layer that interprets Gemini CLI’s permission requests and translates them into a universal state machine. This article examines the exact mechanism Nodeterm uses to handle Gemini’s ToolPermission notifications, from hook reception to UI rendering.

## Understanding Gemini’s ToolPermission Hook Events

Gemini communicates permission requirements through a specific hook event taxonomy. When the Gemini CLI requires user approval to execute a tool, it emits a hook event with `hook_event_name` set to `"Notification"` and a `notification_type` of `"ToolPermission"`.

Unlike Claude’s multi-variant permission system, Gemini consolidates all permission requests into this single notification type. This design simplifies the parsing logic but requires Nodeterm to treat every instance as a blocking operation that demands immediate user attention.

## Normalizing ToolPermission Events in normalize.ts

The core logic for handling these notifications resides in [`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts). Here, the `normalizeGemini` function inspects incoming hook payloads and applies a rigid state mapping that converts Gemini-specific events into Nodeterm’s standardized agent state format.

### Hook Reception and Parsing

When a payload arrives from the Gemini CLI, the normalizer first checks if the `hook_event_name` equals `"Notification"`. If true, it inspects the `notification_type` field to determine the appropriate state mapping. According to the source code at line 394, the implementation specifically filters for the exact string `"ToolPermission"`:

```typescript
// src/shared/agents/normalize.ts – Gemini normalization
if (env.hook_event_name === 'Notification') {
  // Gemini sends a single permission request type.
  if (p.notification_type === 'ToolPermission') {
    // Map to a blocked state so the UI knows the agent is awaiting approval.
    return { state: 'blocked', ...commonFields };
  }
}

```

This conditional branch acts as a strict gate. Only payloads matching both the hook event name and notification type trigger the blocked state transition.

### State Mapping to "blocked"

Upon detecting a ToolPermission notification, the function returns a `NormalizedAgentEvent` object with its `state` field explicitly set to `"blocked"`. This normalized state serves as a universal signal across Nodeterm’s architecture, indicating that the agent cannot proceed without external intervention.

The `"blocked"` state abstracts away Gemini-specific implementation details, allowing the UI layer to handle permission prompts consistently regardless of which underlying AI agent generated the request.

## UI Representation and User Interaction

Once the normalized event propagates to the renderer, Nodeterm’s terminal interface reacts by displaying a visual blocker. The component responsible for this behavior resides in [`src/renderer/terminal/agent-restart.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/terminal/agent-restart.ts).

When the renderer detects an agent state of `"blocked"`, it renders a red badge on the Gemini node and pauses execution:

```typescript
// src/renderer/terminal/agent-restart.ts – UI reacts to blocked state
if (agentState === 'blocked') {
  // Show a permission dialog; on user approval, send the appropriate command.
  showPermissionPrompt(nodeId, () => sendApprovalCommand(nodeId));
}

```

The `showPermissionPrompt` function displays the specific tool permission request to the user. When the user clicks the approval prompt, Nodeterm dispatches the appropriate command back to the Gemini CLI, which clears the blocked state and resumes agent execution.

## Key Implementation Files

Several files work in concert to ensure reliable handling of Gemini’s permission system:

- **[`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts)** – Parses Gemini hook payloads and maps `ToolPermission` notifications to a blocked state at line 394.
- **[`src/shared/agents/hook-events.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/hook-events.ts)** – Documents the Gemini hook event taxonomy, highlighting the single permission notification type.
- **[`src/renderer/terminal/agent-restart.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/terminal/agent-restart.ts)** – Implements the UI logic that displays the blocked badge and handles user approval callbacks.
- **[`src/renderer/terminal/agent-restart.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/terminal/agent-restart.test.ts)** – Contains test coverage ensuring Gemini nodes correctly transition to the blocked state when permission notifications arrive.

## Summary

- Nodeterm intercepts Gemini’s ToolPermission notifications in [`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts) by checking for `hook_event_name === 'Notification'` and `notification_type === 'ToolPermission'`.
- The normalizer converts these events into a universal `"blocked"` state via the `normalizeGemini` function.
- The renderer displays a red blocked badge and pauses execution until the user explicitly approves the tool usage through the UI.
- This architecture mirrors Claude’s permission handling but is deliberately simplified to accommodate Gemini’s single notification type.

## Frequently Asked Questions

### How does Nodeterm distinguish between different types of Gemini notifications?

Nodeterm checks the `notification_type` field within the hook payload. Currently, Nodeterm only implements specific handling for the `"ToolPermission"` value, treating all other notification types as non-blocking events. This is implemented in the conditional logic at line 394 of [`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts).

### What happens if the user denies a ToolPermission request?

When the user denies permission through the UI prompt in [`src/renderer/terminal/agent-restart.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/terminal/agent-restart.ts), Nodeterm sends a denial command back to the Gemini CLI. The agent remains in the `"blocked"` state until the user either approves the request or terminates the session, preventing unauthorized tool execution.

### Is the blocked state unique to Gemini, or do other agents use it?

The `"blocked"` state is a universal abstraction used across all supported agents in Nodeterm. While the repository implements specific parsing logic for Gemini’s `ToolPermission` notifications, the resulting state matches Claude’s permission prompt handling and other agents, ensuring consistent UI behavior regardless of the underlying AI provider.

### Where can I find the test coverage for ToolPermission handling?

The test suite in [`src/renderer/terminal/agent-restart.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/terminal/agent-restart.test.ts) validates that Gemini nodes correctly transition to the blocked state when receiving ToolPermission notifications. These tests verify both the state mapping logic and the UI components responsible for rendering permission prompts.