# https://github.com/humanlayer/humanlayer Project Manual

Generated at: 2026-06-04 02:34:37 UTC

## Table of Contents

- [System Overview and Architecture](#page-1)
- [hld Daemon: Sessions, Approvals, MCP, and Storage](#page-2)
- [CodeLayer Desktop (humanlayer-wui)](#page-3)
- [hlyr CLI, SDKs, Contracts, and Release Pipeline](#page-4)

<a id='page-1'></a>

## System Overview and Architecture

### Related Pages

Related topics: [hld Daemon: Sessions, Approvals, MCP, and Storage](#page-2), [CodeLayer Desktop (humanlayer-wui)](#page-3), [hlyr CLI, SDKs, Contracts, and Release Pipeline](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [README.md](https://github.com/humanlayer/humanlayer/blob/main/README.md)
- [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md)
- [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json)
- [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md)
- [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts)
- [hld/sdk/typescript/src/generated/models/ConversationEvent.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/ConversationEvent.ts)
- [hld/sdk/typescript/src/generated/models/Approval.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/Approval.ts)
- [hld/sdk/typescript/src/generated/models/MCPServer.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/MCPServer.ts)
- [hld/sdk/typescript/src/generated/apis/FilesApi.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/apis/FilesApi.ts)
- [humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)
- [humanlayer-wui/src/lib/telemetry/posthog-sanitizer.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/telemetry/posthog-sanitizer.ts)
- [hlyr/src/thoughtsConfig.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/thoughtsConfig.ts)
- [hack/linear/README.md](https://github.com/humanlayer/humanlayer/blob/main/hack/linear/README.md)
- [apps/react/src/frontend.tsx](https://github.com/humanlayer/humanlayer/blob/main/apps/react/src/frontend.tsx)

</details>

# System Overview and Architecture

## Purpose and Scope

HumanLayer is an open source platform that provides a unified surface for orchestrating AI coding agents. Its flagship product, **CodeLayer**, is described as "the best way to get Coding Agents to solve hard problems in complex codebases" — a keyboard-first IDE that wraps Claude Code and exposes "battle-tested workflows" for AI-first development ([README.md](https://github.com/humanlayer/humanlayer/blob/main/README.md)).

The system is decomposed into several cooperating packages that together deliver:

- A desktop-class web UI for managing agent sessions, approvals, and conversations.
- A long-running **daemon** (`hld`) that brokers sessions, MCP servers, and tool-call approvals.
- A command-line tool (`hlyr`) for direct human-in-the-loop contact, Claude Code configuration, and developer notes.
- A generated TypeScript SDK that the UI uses to talk to the daemon over HTTP and Server-Sent Events.
- A curated set of `hack/` utilities (e.g. a Linear CLI) that extend the platform.

## High-Level Component Map

```mermaid
graph TD
    User[Developer / Team]
    CodeLayerUI[CodeLayer Desktop UI<br/>(humanlayer-wui)]
    TauriShell[Tauri Shell]
    CLI[hlyr CLI]
    Daemon[hld Daemon<br/>Sessions, Approvals, MCP]
    Claude[Claude Code<br/>SDK / CLI]
    MCP[(MCP Servers)]
    Slack[Slack / Email / Web]
    PostHog[(PostHog Telemetry)]
    Linear[Linear API]

    User -->|interacts| CodeLayerUI
    User -->|scripts & CI| CLI
    CodeLayerUI --> TauriShell
    TauriShell -->|HTTP + SSE| Daemon
    CLI -->|contact_human| Slack
    CLI -->|claude init| User
    Daemon -->|spawns| Claude
    Daemon -->|manages| MCP
    Daemon -->|events| CodeLayerUI
    CodeLayerUI -->|sanitized events| PostHog
    CLI -->|thoughts sync| User
    Linear -.->|via hack/linear| User
```

The diagram reflects three deployment surfaces (UI, CLI, and a third-party Linear CLI in `hack/linear/`) that all converge on the daemon and on external contact channels ([hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md), [hack/linear/README.md](https://github.com/humanlayer/humanlayer/blob/main/hack/linear/README.md)).

## Repository Layout

The repository is a monorepo containing several distinct workspaces. Each top-level folder corresponds to one deployable artifact or shared library.

| Path | Type | Responsibility |
|------|------|----------------|
| `humanlayer-wui/` | Vite + React 19 + Tauri app | Desktop-class UI for CodeLayer ([humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json)). |
| `hld/` | Daemon (Go) | Hosts sessions, approvals, files, MCP and emits SSE events. |
| `hld/sdk/typescript/` | Generated SDK | Typed HTTP + SSE client used by the UI ([hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts)). |
| `hlyr/` | Node CLI | `contact_human`, `claude init`, `thoughts` subcommands ([hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md)). |
| `apps/react/` | Standalone React app | Generic Vite/React entry point ([apps/react/src/frontend.tsx](https://github.com/humanlayer/humanlayer/blob/main/apps/react/src/frontend.tsx)). |
| `hack/linear/` | Utility CLI | Linear issue tracker integration ([hack/linear/README.md](https://github.com/humanlayer/humanlayer/blob/main/hack/linear/README.md)). |
| `humanlayer-wui/src/lib/daemon/types.ts` | Shared types | UI-side mirrors of daemon types ([humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)). |

## The Daemon (`hld`)

The `hld` daemon is the central control plane. It exposes REST APIs grouped by resource family and pushes live updates through Server-Sent Events.

### API Surface

The generated TypeScript SDK is grouped into resource APIs. The class `HLDClient` instantiates each one with a single base URL:

| API | Purpose | Notes |
|-----|---------|-------|
| `SessionsApi` | Create, list, continue, archive sessions | Primary interaction surface for CodeLayer. |
| `ApprovalsApi` | List, fetch, respond to pending approvals | Drives the human-in-the-loop flow. |
| `SystemApi` | Health and capability discovery | Used by the UI on startup. |
| `SettingsApi` | User and daemon configuration | Includes `UserSettingsResponse` and `UpdateUserSettingsRequest`. |
| `FilesApi` | Create directories, fuzzy-search files | Powers command palette and `@`-mentions ([hld/sdk/typescript/src/generated/apis/FilesApi.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/apis/FilesApi.ts)). |
| `AgentsApi` | Discover Claude Code agents and sub-agents | Includes `DiscoverAgents200Response`. |

Source: [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts).

### Real-Time Event Stream

The daemon emits a continuous stream of events that the UI consumes. The client manages a per-channel `EventSourceLike` connection per SSE channel, with explicit `onMessage`, `onError`, `onConnect`, and `onDisconnect` handlers — important for reconnecting after laptop sleep or daemon restarts ([hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts)).

Key event shapes include:

- **Session lifecycle**: `SessionStatusChangedEventData` carries `old_status` and `new_status` so the UI can animate transitions deterministically.
- **Approvals**: `NewApprovalEventData` (and its older alias `ApprovalRequestedEventData`) signals that a tool call is awaiting human input, and `ApprovalResolvedEventData` carries the `Decision` back to the daemon.
- **Settings changes**: `SessionSettingsChangedEventData` reports mutations to `auto_accept_edits`, `dangerously_skip_permissions`, and the timeout. A `SessionSettingsChangeReason.EXPIRED` value indicates the "dangerous skip permissions" window elapsed ([humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)).

### Core Data Models

The daemon's contract is encoded in TypeScript types. The most important ones are summarized below.

| Model | Key Fields | Notes |
|-------|------------|-------|
| `ConversationEvent` | `eventType`, `role`, `toolName`, `toolResultContent`, `isCompleted`, `approvalStatus`, `approvalId` | `eventType` is one of `message`, `tool_call`, `tool_result`, `system`, `thinking` ([hld/sdk/typescript/src/generated/models/ConversationEvent.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/ConversationEvent.ts)). |
| `Approval` | `id`, `runId`, `sessionId`, `status`, `createdAt`, `respondedAt`, `toolName`, `toolInput`, `comment` | `status` transitions through `pending`, `approved`, `denied`, `resolved` ([hld/sdk/typescript/src/generated/models/Approval.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/Approval.ts)). |
| `MCPServer` | `type`, `command`, `args`, `env`, `url`, `headers` | Covers both stdio (`command`/`args`/`env`) and HTTP (`url`/`headers`) transports ([hld/sdk/typescript/src/generated/models/MCPServer.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/MCPServer.ts)). |

## CodeLayer UI (`humanlayer-wui`)

The UI is a Vite-powered React 19 application wrapped in Tauri for desktop distribution. The package manifest declares both the desktop and the Storybook toolchains ([humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json)).

### Tech Stack

| Concern | Library / Tool |
|---------|----------------|
| Framework | React 19 (`^19.1.0`) with React Router 7. |
| Build | Vite + Tauri 2. |
| Styling | Tailwind CSS 4, `tailwind-merge`, `class-variance-authority`. |
| State | Zustand 5 (with documented slice patterns). |
| Editor | TipTap 3 (with `lowlight` for code highlighting, `react-syntax-highlighter`). |
| UX primitives | Radix UI (checkbox, dialog, dropdown, popover, scroll-area, select, slot, collapsible, label). |
| Telemetry | `posthog-js` with a custom sanitizer. |
| Notifications | `sonner`. |
| Command palette | `cmdk`. |
| Hotkeys | `react-hotkeys-hook`. |
| Testing | Bun test, Testing Library, Storybook 9. |

Source: [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json).

### Daemon Integration

The UI consumes `@humanlayer/hld-sdk` directly from the sibling workspace at `file:../hld/sdk/typescript`. The client is constructed with an explicit `baseUrl` or `port`, optional headers, and an `onFetchError` hook that is invoked with the URL and HTTP method whenever a request fails — making it the natural place to surface network errors to the user ([hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts)).

On the UI side, `humanlayer-wui/src/lib/daemon/types.ts` mirrors the same shapes (`Approval`, `SessionStatus`, `ConversationEvent`, `ContinueSessionRequest`, etc.) so React components can use locally typed helpers without going through the SDK. The `ApprovalRequestedEventData` alias is explicitly marked "to be removed", signaling an ongoing cleanup of the type surface ([humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)).

### State Management

State is composed of focused Zustand slices. The documented patterns include:

- **Conditional updates** that propagate changes to a "focused" entity when it is the same one being mutated.
- **Workflow actions** that coordinate across slices (e.g. removing an approval and updating the related session's status to `Running`).
- **Slice composition** in `src/stores/demo/README.md`, which serves as a worked example for contributors writing new stores ([humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md)).

### Telemetry and Privacy

PostHog is the product analytics provider, but every event is filtered through a sanitizer that whitelists only safe keys. The whitelist explicitly includes PostHog-standard fields (`$current_url`, `$browser`, `$os`, `$screen_height`, `distinct_id`, etc.) plus a small set of UX-meaningful names (`theme`, `theme_source`, `menu_opened`, `dialog_shown`). Recursion is bounded at depth 10, and any filtered key emits a `console.warn` for developer visibility ([humanlayer-wui/src/lib/telemetry/posthog-sanitizer.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/telemetry/posthog-sanitizer.ts)). This is consistent with the project's emphasis on not leaking session or code content.

### UX Surfaces the Community Cares About

Several GitHub discussions are directly relevant to the UI's architecture:

- **Issue #956** requests a way to *disallow* the "Disable bypass permissions" toggle entirely for security-conscious teams. The daemon already models `dangerously_skip_permissions` and a `dangerously_skip_permissions_timeout_ms` per session, and emits a `SessionSettingsChangeReason.EXPIRED` event when that window closes — so the policy hook should attach to the settings pipeline rather than the UI ([humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)).
- **Issue #944** complains about the auto-scroll behavior when hovering a conversation step. Auto-scroll in CodeLayer is a UI-level concern driven by the `ConversationEvent` stream; it lives in the renderer that maps `eventType` to the rendered block.
- **Issue #905** asks for a `CMD+C` shortcut to archive and recreate a session. The session lifecycle types (`SessionStatus`, `ListSessionsRequest`) are already in place to support such a flow, and any new shortcut would simply orchestrate the existing `archive` + `create` API calls.

## The CLI (`hlyr`)

`hlyr` is the developer-facing surface for non-UI workflows. It bundles several subcommands around a shared configuration system ([hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md)).

### Subcommand Inventory

| Subcommand | Description |
|------------|-------------|
| `contact_human` | Sends a message to a human via Slack, email, or the web UI. Reads `HUMANLAYER_API_KEY`, `HUMANLAYER_SLACK_CHANNEL`, or `HUMANLAYER_EMAIL_ADDRESS`; falls back to `--config-file` (e.g. `.hlyr.json`). |
| `claude init` | Copies Claude Code commands, agents, and settings into a project's `.claude/` directory. Supports `--all` (non-interactive) and `--force`. |
| `thoughts` | Manages a developer-notes repository (per-project and global), with **profile** support: `thoughts profile create / list / show / delete`. |
| MCP server mode | Runs the MCP server so external tools (e.g. Claude Code) can talk back to HumanLayer for approvals. |

### Profile-Based Thoughts

The `thoughts` subsystem is the most configuration-heavy part of the CLI. `hlyr/src/thoughtsConfig.ts` defines a `ResolvedProfileConfig` with `thoughtsRepo` and `reposDir` fields, and the file exposes overloaded `updateSymlinksForNewUsers` signatures so the same code path supports both the legacy four-string call and the newer profile-aware call ([hlyr/src/thoughtsConfig.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/thoughtsConfig.ts)). This back-compat design is what allows "per-repository profiles, even worktrees of the same repo" to coexist with the original single-repo mode ([hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md)).

## Cross-Component Data Flow

The diagram below traces a single approval lifecycle end to end. This is the canonical interaction in HumanLayer and ties together the UI, daemon, Claude Code, and a human approver.

```mermaid
graph TD
    A[User types in CodeLayer] --> B[hld creates Session]
    B --> C[Session invokes Claude Code]
    C --> D{Tool call needs approval?}
    D -- No --> E[Tool executes]
    D -- Yes --> F[Approval status: pending]
    F --> G[SSE push to CodeLayer UI]
    G --> H[UI shows approval card]
    H --> I[User clicks Approve/Deny]
    I --> J[ApprovalsApi responds]
    J --> K[SSE: ApprovalResolvedEvent]
    K --> L[Session continues]
    L --> C
    E --> L
```

Source: [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts), [hld/sdk/typescript/src/generated/models/Approval.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/Approval.ts), [hld/sdk/typescript/src/generated/models/ConversationEvent.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/ConversationEvent.ts).

## Configuration Surfaces

Configuration is layered: each tool reads from a mix of environment variables, JSON config files, and CLI flags. The `hlyr` config file shape (`.hlyr.json`) is the canonical example, defining channels for `slack` with a `channel_or_user_id`:

```json
{
  "channel": {
    "slack": {
      "channel_or_user_id": "C08G5C3V552"
    }
  }
}
```

For the daemon, the relevant axes are:

- `auto_accept_edits` and `dangerously_skip_permissions` toggles, surfaced through `SessionSettingsChangedEventData`.
- `dangerously_skip_permissions_timeout_ms` — a server-side timeout that drives the `EXPIRED` reason event ([humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)).
- `MCP` server descriptors with per-server `command`, `args`, `env`, `url`, and `headers` ([hld/sdk/typescript/src/generated/models/MCPServer.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/MCPServer.ts)).
- `HUMANLAYER_API_KEY` and the channel-specific `HUMANLAYER_SLACK_CHANNEL` / `HUMANLAYER_EMAIL_ADDRESS` env vars ([hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md)).

## Build and Tooling

The repository's `package.json` for the UI exposes a familiar set of scripts: `dev`, `build` (which runs `tsc && vite build`), `lint`, `format`, `typecheck`, a combined `check` that runs all three, `test` (Bun), and Storybook (`storybook`, `build-storybook`). Tauri is invoked through `tauri` ([humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json)). The CLI exposes the equivalent lifecycle in its own README: `npm install`, `npm run build`, `npm test`, and `npm run dev` for watch mode ([hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md)).

The generic React entry point at `apps/react/src/frontend.tsx` is intentionally minimal — it renders an `<App />` into `#root` inside `StrictMode`, with HMR-aware re-rendering — confirming that the heavier orchestration lives in `humanlayer-wui` rather than `apps/react` ([apps/react/src/frontend.tsx](https://github.com/humanlayer/humanlayer/blob/main/apps/react/src/frontend.tsx)).

## Extending the System

Two extension points stand out for new contributors:

1. **New `hlyr` subcommands** follow the pattern of grouping under a top-level verb (e.g. `thoughts`, `claude`, `contact_human`). New thoughts features land alongside `thoughtsConfig.ts`, which already models the `ResolvedProfileConfig` and symlink-update logic for multi-profile setups ([hlyr/src/thoughtsConfig.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/thoughtsConfig.ts)).
2. **New SDK API groups** should be added as both a generated `apis/*Api.ts` and a corresponding `*Api` field on the `HLDClient` class in `hld/sdk/typescript/src/client.ts`. The existing six groups (Sessions, Approvals, System, Settings, Files, Agents) set the convention for argument shapes, method prefixes (`*Raw` for `runtime.ApiResponse<T>`), and JS doc summaries.

## Common Failure Modes and Troubleshooting

| Symptom | Likely Cause | Where to Look |
|---------|--------------|---------------|
| SSE stream stalls after the laptop sleeps | The `EventSourceLike` needs a manual reconnect; UI depends on the SDK's `onDisconnect` handler firing | [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts) |
| Tool call "hangs" without surfacing in the UI | An `Approval` is still in `pending` state; check `ApprovalsApi.listApprovals` | [hld/sdk/typescript/src/generated/models/Approval.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/Approval.ts) |
| "Dangerous skip permissions" toggle stops working mid-session | Daemon emitted `SessionSettingsChangeReason.EXPIRED` | [humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts) |
| `humanlayer thoughts` writes to the wrong repo | Profile resolution; verify `thoughts profile list` and `thoughts config` | [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md) |
| `humanlayer claude init` errors in CI | The command requires a TTY unless `--all` is passed | [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md) |
| PostHog events appear empty in dashboards | Sanitizer stripped the keys; check `console.warn` output from `sanitizeEventProperties` | [humanlayer-wui/src/lib/telemetry/posthog-sanitizer.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/telemetry/posthog-sanitizer.ts) |
| `linear` CLI returns "command not found" | PATH not set up; rerun `npm install -g .` inside `hack/linear/` | [hack/linear/README.md](https://github.com/humanlayer/humanlayer/blob/main/hack/linear/README.md) |

## See Also

- [hlyr CLI Reference](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md) — Detailed subcommand docs for `contact_human`, `claude init`, and `thoughts`.
- [humanlayer-wui Stores Guide](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) — Patterns for composing Zustand slices in the UI.
- [hack/linear CLI](https://github.com/humanlayer/humanlayer/blob/main/hack/linear/README.md) — Companion utility for pulling Linear tickets into a `thoughts/shared/tickets/` directory.

---

<a id='page-2'></a>

## hld Daemon: Sessions, Approvals, MCP, and Storage

### Related Pages

Related topics: [System Overview and Architecture](#page-1), [CodeLayer Desktop (humanlayer-wui)](#page-3), [hlyr CLI, SDKs, Contracts, and Release Pipeline](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [hld/cmd/hld/main.go](https://github.com/humanlayer/humanlayer/blob/main/hld/cmd/hld/main.go)
- [hld/daemon/daemon.go](https://github.com/humanlayer/humanlayer/blob/main/hld/daemon/daemon.go)
- [hld/session/manager.go](https://github.com/humanlayer/humanlayer/blob/main/hld/session/manager.go)
- [hld/session/claudecode_wrapper.go](https://github.com/humanlayer/humanlayer/blob/main/hld/session/claudecode_wrapper.go)
- [hld/session/permission_monitor.go](https://github.com/humanlayer/humanlayer/blob/main/hld/session/permission_monitor.go)
- [hld/approval/manager.go](https://github.com/humanlayer/humanlayer/blob/main/hld/approval/manager.go)
- [hld/mcp/server.go](https://github.com/humanlayer/humanlayer/blob/main/hld/mcp/server.go)
- [hld/store/sqlite.go](https://github.com/humanlayer/humanlayer/blob/main/hld/store/sqlite.go)
- [hld/bus/events.go](https://github.com/humanlayer/humanlayer/blob/main/hld/bus/events.go)
- [hld/rpc/server.go](https://github.com/humanlayer/humanlayer/blob/main/hld/rpc/server.go)
- [hld/api/handlers/server.go](https://github.com/humanlayer/humanlayer/blob/main/hld/api/handlers/server.go)
- [hld/api/openapi.yaml](https://github.com/humanlayer/humanlayer/blob/main/hld/api/openapi.yaml)
- [hld/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/README.md)
- [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts)
- [hld/sdk/typescript/src/generated/models/Approval.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/Approval.ts)
- [hld/sdk/typescript/src/generated/models/FileSnapshot.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/FileSnapshot.ts)
- [hld/sdk/typescript/src/generated/models/ContinueSessionResponseData.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/ContinueSessionResponseData.ts)
- [hld/sdk/typescript/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/README.md)
- [humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)
</details>

# hld Daemon: Sessions, Approvals, MCP, and Storage

## Overview

The **HumanLayer Daemon (hld)** is the long-running backend service that orchestrates Claude Code sessions, mediates human-in-the-loop approval workflows, exposes a Model Context Protocol (MCP) server to the underlying agent, and persists session/approval state in a local SQLite database. It is the authoritative control plane that the WUI (`humanlayer-wui`) and the CLI (`hlyr`) speak to, and the bridge that connects a Claude Code subprocess to channels such as Slack, email, and the web UI for human contact.

Per the daemon's own README, hld provides "a REST API and JSON-RPC interface for managing Claude Code sessions, approvals, and real-time event streaming" — three concerns that map almost one-to-one to the three primary subsystems documented on this page.

Source: [hld/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/README.md)

## High-Level Architecture

The daemon is composed of loosely coupled managers that communicate through an in-process event bus. The HTTP server (REST + SSE) and the JSON-RPC server are both thin front doors onto the same internal managers.

```mermaid
graph TD
    CLI[hlyr CLI]
    WUI[humanlayer-wui / CodeLayer]
    Ext[External Integrations / SDKs]

    subgraph HLD[hld Daemon]
        HTTP[REST API + SSE<br/>port 7777]
        RPC[JSON-RPC Server]
        SM[Session Manager]
        AM[Approval Manager]
        MCP[MCP Server]
        BUS[(Event Bus)]
        STORE[(SQLite Store)]
        CCW[Claude Code Wrapper]
        PM[Permission Monitor]
    end

    CC[Claude Code Subprocess]
    SLACK[Slack / Email / Web UI]

    CLI --> HTTP
    WUI --> HTTP
    Ext --> HTTP
    CLI --> RPC
    Ext --> RPC

    HTTP --> SM
    HTTP --> AM
    RPC --> SM
    RPC --> AM
    RPC --> MCP

    SM --> CCW
    SM --> PM
    SM --> BUS
    SM --> STORE
    AM --> BUS
    AM --> STORE
    MCP --> AM
    BUS --> HTTP

    CCW <--> CC
    AM --> SLACK
    SLACK --> AM
```

**Key responsibilities of each subsystem**

| Subsystem | File | Responsibility |
|---|---|---|
| Entrypoint | `hld/cmd/hld/main.go` | Parse flags, load config, wire the daemon together, signal handling |
| Daemon lifecycle | `hld/daemon/daemon.go` | Long-running supervisor that owns all managers and graceful shutdown |
| Session management | `hld/session/manager.go` | Create / continue / interrupt / archive Claude Code sessions |
| Claude Code integration | `hld/session/claudecode_wrapper.go` | Spawns and tracks the `claude` subprocess, streams output back |
| Permission monitor | `hld/session/permission_monitor.go` | Watches tool calls and decides when human approval is required |
| Approvals | `hld/approval/manager.go` | Lifecycle of approval requests: pending → approved/denied, with retries |
| MCP | `hld/mcp/server.go` | Exposes `request_approval` and friends to the agent via MCP |
| Storage | `hld/store/sqlite.go` | Persistent store for sessions, approvals, file snapshots, settings |
| Event bus | `hld/bus/events.go` | Pub/sub channel that all managers publish to and that SSE consumers subscribe from |
| RPC transport | `hld/rpc/server.go` | JSON-RPC endpoint used by the CLI and SDK |
| REST transport | `hld/api/handlers/server.go` | HTTP handlers generated from the OpenAPI spec |
| API contract | `hld/api/openapi.yaml` | Source of truth for the public REST surface |

Source: [hld/cmd/hld/main.go](https://github.com/humanlayer/humanlayer/blob/main/hld/cmd/hld/main.go), [hld/daemon/daemon.go](https://github.com/humanlayer/humanlayer/blob/main/hld/daemon/daemon.go), [hld/bus/events.go](https://github.com/humanlayer/humanlayer/blob/main/hld/bus/events.go)

## Configuration

The daemon reads a small set of environment variables, all prefixed with `HUMANLAYER_DAEMON_`. The HTTP transport can be disabled entirely when only Unix sockets / JSON-RPC are required.

| Variable | Default | Purpose |
|---|---|---|
| `HUMANLAYER_DAEMON_HTTP_PORT` | `7777` | TCP port for the REST + SSE server. Set to `0` to disable HTTP. |
| `HUMANLAYER_DAEMON_HTTP_HOST` | `127.0.0.1` | Bind address for the HTTP server. Loopback by default for security. |

To run a Unix-socket-only daemon (no REST listener):

```bash
export HUMANLAYER_DAEMON_HTTP_PORT=0
hld start
```

Source: [hld/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/README.md)

## Sessions

### Session Lifecycle

A session is a single Claude Code conversation tracked by the daemon. Sessions can be created fresh, continued from a prior point, interrupted, or archived. Each session is identified by both a daemon-side `sessionId`/`runId` and the upstream `claudeSessionId` that the underlying `claude` subprocess returns.

```mermaid
stateDiagram-v2
    [*] --> Drafting
    Drafting --> Running: launch
    Running --> WaitingInput: tool call requires approval
    WaitingInput --> Running: approval granted
    WaitingInput --> Running: deny + retry policy
    WaitingInput --> Completed: deny + abort
    Running --> Completed: agent finishes
    Running --> Failed: subprocess error
    Running --> Archived: user archives
    Completed --> Archived: user archives
    Failed --> [*]
    Archived --> [*]
```

The WUI extends the SDK session type with a couple of UI-specific fields but otherwise consumes the wire format directly:

```typescript
export interface Session extends SDKSession {
  additionalDirectories?: string[]
}
```

Source: [humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)

### Session Manager

`hld/session/manager.go` owns:

- Persisting session metadata in SQLite (title, working directory, model, parent session, timestamps).
- Driving the Claude Code wrapper to start a new run or continue an existing one.
- Subscribing to the event bus to react to approval outcomes and tool events.
- Emitting `SessionStatus` transitions to subscribers.

### Claude Code Wrapper

`hld/session/claudecode_wrapper.go` is a thin adapter around the `claude` CLI. It is responsible for:

- Spawning the subprocess with the right model, working dir, allowed tools, and permission-prompt tool.
- Streaming the subprocess's output (text, JSON, or stream-JSON modes) into the event bus.
- Forwarding approval responses back into the subprocess.

The Go SDK (`claudecode-go`) mirrors this surface for embedders that want to drive Claude Code directly without the daemon.

Source: [claudecode-go/README.md](https://github.com/humanlayer/humanlayer/blob/main/claudecode-go/README.md)

### Continuing a Session

Continuing an existing session produces a new run that retains the original conversation context. The REST contract for this operation is generated from the OpenAPI spec:

```typescript
export interface ContinueSessionResponseData {
    sessionId: string
    runId: string
    claudeSessionId: string
    parentSessionId: string
}
```

The four IDs let the WUI distinguish between the daemon run, the upstream Claude conversation, and the logical session tree.

Source: [hld/sdk/typescript/src/generated/models/ContinueSessionResponseData.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/ContinueSessionResponseData.ts)

### Community feedback: archiving sessions

A long-standing request from the community is to make session archival a single keystroke. Currently the WUI flow is roughly: press `E` → press `C` → type a name → change directory. A `CMD+C` shortcut for "archive current session and start a fresh one" is a requested UX improvement, which would map to a bulk-archive + new-draft operation against the REST API.

The REST surface already supports bulk archive (the generated `BulkArchiveResponse` model is part of the SDK), so a one-keystroke command is a thin UI feature on top of existing daemon capabilities.

Source: [hld/sdk/typescript/src/generated/models/BulkArchiveResponse.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/BulkArchiveResponse.ts)

## Approvals

### Approval Workflow

Approvals are the human-in-the-loop gate. When the permission monitor detects a tool call that is not in the auto-allow set, it asks the approval manager to suspend the agent and surface a request to a human.

```mermaid
graph TD
    A[Claude Code emits tool call] --> B{Permission Monitor}
    B -- Auto-allow --> C[Tool executes]
    B -- Requires human --> D[Approval Manager creates pending approval]
    D --> E[Event Bus: approval.pending]
    E --> F[SSE: pushes to WUI]
    E --> G[Routing engine: Slack / Email / Web]
    F --> H{User decides}
    G --> H
    H -- Approve --> I[Status = approved]
    H -- Deny --> J[Status = denied]
    H -- Deny + retry --> K[Status = denied, retry policy applied]
    I --> L[Tool execution continues]
    J --> M[Session aborted or continues without tool]
    K --> N[Agent is re-prompted]
    L --> E2[Event Bus: approval.resolved]
    J --> E2
    K --> E2
    E2 --> F2[SSE: pushes resolution to WUI]
```

### Approval Data Model

The canonical approval object, as exposed to TypeScript clients, is:

| Field | Type | Description |
|---|---|---|
| `id` | string | Daemon-internal approval identifier |
| `runId` | string | Run the approval belongs to |
| `sessionId` | string | Session the approval belongs to |
| `status` | `ApprovalStatus` enum | `pending`, `approved`, `denied`, etc. |
| `createdAt` | Date (ISO 8601) | When the request was created |
| `respondedAt` | Date? | When the human responded |
| `toolName` | string | Tool the agent is calling |
| `toolInput` | object | JSON input parameters to the tool |
| `comment` | string? | Free-text comment from the approver |

A guard `instanceOfApproval` is generated alongside the type so SDK consumers can validate untyped JSON before trusting it.

Source: [hld/sdk/typescript/src/generated/models/Approval.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/Approval.ts)

### Approval Manager

`hld/approval/manager.go` owns the approval queue and the state transitions shown above. It is also the integration point with external contact channels (Slack, email) configured via the CLI:

```bash
export HUMANLAYER_API_KEY=...
export HUMANLAYER_SLACK_CHANNEL=C08G5C3V552
humanlayer contact_human --message "Review this PR"
```

Source: [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md)

### Bypass-permissions policy (community concern)

Enterprise users have asked for a way to *disallow* the "Disable bypass permissions" toggle so that no agent run can opt out of the permission system. The toggle maps directly to the `dangerouslySkipPermissions` field on `LaunchSessionParams`:

```typescript
export interface LaunchSessionParams {
  query: string
  title?: string
  provider?: 'anthropic' | 'openrouter' | 'baseten'
  model?: string
  workingDir?: string
  mcpConfig?: any
  permissionPromptTool?: string
  maxTurns?: number
  autoAcceptEdits?: boolean
  dangerouslySkipPermissions?: boolean  // <-- community concern
  proxyApiKey?: string
  additionalDirectories?: string[]
  draft?: boolean
}
```

A deployment-level hard-disable would be a configuration knob read by the daemon (or a WUI feature flag) that rejects any `createSession` request with `dangerouslySkipPermissions: true`, rather than a per-user toggle. Tracking that policy is the responsibility of the launch path in `hld/session/manager.go` and the launch UI in the WUI.

Source: [humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)

## MCP Server

`hld/mcp/server.go` exposes a Model Context Protocol server that the agent (Claude Code) talks to during a run. The MCP surface typically includes tools such as `request_approval` and `get_approval_response`, but the daemon treats MCP as just another consumer of the approval manager: every tool call routed through MCP is translated into a normal approval lifecycle event, which the event bus fans out to the WUI and external channels.

This indirection means that:

- The WUI does not need to know which transport the agent used.
- External integrations (Slack bots, email responders) implement the same `pending → resolved` contract regardless of whether the call originated from MCP, REST, or RPC.
- Replacing the agent runtime (e.g., switching to a different MCP-capable model) does not require touching the approval manager.

Source: [hld/mcp/server.go](https://github.com/humanlayer/humanlayer/blob/main/hld/mcp/server.go), [hld/approval/manager.go](https://github.com/humanlayer/humanlayer/blob/main/hld/approval/manager.go)

## Storage

### SQLite Store

`hld/store/sqlite.go` is the only persistent store in the system. It is intentionally simple (single file, no external service) so the daemon can be installed and run locally with no infrastructure dependencies.

Tables (inferred from the API surface and generated models):

| Table | Purpose | Key columns |
|---|---|---|
| `sessions` | One row per session + versioned run | `id`, `run_id`, `claude_session_id`, `parent_session_id`, `status`, `working_dir`, `model`, `created_at`, `archived_at` |
| `approvals` | Pending and historical approvals | `id`, `run_id`, `session_id`, `status`, `tool_name`, `tool_input` (JSON), `comment`, `created_at`, `responded_at` |
| `file_snapshots` | Per-tool pre-edit snapshots used for diff/undo | `tool_id`, `file_path`, `content`, `created_at` |
| `settings` | User config and per-deployment policy | key/value |
| `events` (optional cache) | Optional durable event log feeding SSE replay | derived from `bus` |

### File Snapshots

When the agent edits a file, the wrapper (or the approval manager) records the pre-edit contents so the WUI can render a diff and offer one-click revert. The shape of each snapshot row is:

```typescript
export interface FileSnapshot {
    toolId: string         // Tool invocation that created the snapshot
    filePath: string
    content: string        // Full file content at snapshot time
    createdAt: Date
}
```

Source: [hld/sdk/typescript/src/generated/models/FileSnapshot.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/FileSnapshot.ts)

### Migration policy

Because schema changes are inevitable, the store implementation is expected to be paired with a sequential migration runner. Production deployments rely on additive migrations (new tables, new nullable columns) and never destructive ones, so archived approvals and historical sessions remain readable across upgrades.

Source: [hld/store/sqlite.go](https://github.com/humanlayer/humanlayer/blob/main/hld/store/sqlite.go)

## Event Bus and Real-Time Streaming

`hld/bus/events.go` is an in-process pub/sub channel. Every state change in the daemon — session created, approval pending, approval resolved, file snapshot taken, agent message streamed — is published exactly once on the bus.

The HTTP server subscribes to the bus and fans events out over Server-Sent Events (SSE) to every connected WUI client. The TypeScript SDK uses the `eventsource` polyfill for Node and the native `EventSource` in the browser:

```typescript
const unsubscribe = await client.subscribeToEvents(
    { sessionId: session.sessionId },
    {
        onMessage: (event) => console.log('Event:', event),
        onError: (error) => console.error('Error:', error),
        onConnect: () => console.log('connected'),
        onDisconnect: () => console.log('disconnected'),
    }
)
```

A community-reported UX issue is that the WUI's auto-scroll on hover currently feels "jumpy" — when a user is reading a long session transcript and hovers over a different step, the view jumps to the top of that block. The fix is purely in the WUI's hover behavior, but the underlying SSE stream provides the events the WUI uses to render steps, so any change in step render boundaries should be validated against live event ordering from the bus.

Source: [hld/sdk/typescript/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/README.md), [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts)

## REST API Surface

The REST API is the canonical, generated integration point. `hld/api/openapi.yaml` is the single source of truth; both the HTTP handlers in `hld/api/handlers/server.go` and the TypeScript SDK in `hld/sdk/typescript/` are generated from it.

```mermaid
graph LR
    SPEC[openapi.yaml] --> GEN[OpenAPI Generator]
    GEN --> HND[Go HTTP handlers<br/>api/handlers/server.go]
    GEN --> SDK[TypeScript SDK<br/>@humanlayer/hld-sdk]
    HND --> HTTP[HTTP server :7777]
    SDK --> WUI[humanlayer-wui]
    SDK --> CLI[hlyr]
    SDK --> EXT[Third-party integrations]
```

The TypeScript SDK exposes a typed client with sub-APIs for the major resource groups:

| Sub-API | Responsibility |
|---|---|
| `SessionsApi` | Create, continue, list, archive, interrupt sessions |
| `ApprovalsApi` | List, fetch, and decide approvals |
| `SystemApi` | Health, version, daemon info |
| `SettingsApi` | Read and update user/deployment settings |
| `FilesApi` | Recent paths, fuzzy file search, directory validation/creation |
| `AgentsApi` | Discover and introspect available agents |

```typescript
const client = new HLDClient({ port: 7777 })
const session = await client.createSession({
    query: "Help me fix a bug",
    model: "claude-3.5-sonnet",
    workingDir: "/path/to/project",
})
const sessions = await client.listSessions({ leafOnly: true })
```

Source: [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts), [hld/api/handlers/server.go](https://github.com/humanlayer/humanlayer/blob/main/hld/api/handlers/server.go)

## End-to-End Testing

The HLD includes comprehensive e2e tests for the REST API. They run an isolated daemon instance against a throwaway SQLite database and exercise every endpoint.

```bash
make e2e-test            # Run all 6 phases
make e2e-test-verbose    # Extra logging
make e2e-test-manual     # Pause for human-in-the-loop approval interaction
KEEP_TEST_ARTIFACTS=true make e2e-test  # Preserve artifacts for debugging
```

The suite covers:

- All 16 REST API endpoints
- SSE event stream validation
- Approval workflows (deny → retry → approve)
- Session lifecycle operations
- Error handling

The tests live under `hld/e2e/` and are split into `test-rest-api.ts` (six test phases) and `test-utils.ts` (test environment plumbing).

Source: [hld/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/README.md)

## Common Failure Modes and Debugging

| Symptom | Likely cause | Where to look |
|---|---|---|
| `HLDClient` cannot connect | Daemon not running, wrong port, or HTTP disabled | Check `HUMANLAYER_DAEMON_HTTP_PORT` and `hld start` |
| SSE stream keeps dropping | Reverse proxy buffering, or `eventsource` polyfill missing | Verify `Content-Type: text/event-stream` and no `Content-Encoding` rewrite |
| Approval stuck in `pending` | External channel (Slack/email) not delivering, or wrong channel ID | Inspect `approval.pending` events on the bus and the CLI's `contact_human` config |
| `dangerouslySkipPermissions` accepted when policy says it shouldn't | WUI does not yet respect deployment-level policy | Track issue #956; needs a launch-time policy check in `session/manager.go` |
| File diff looks wrong | Snapshot taken too late or `toolId` mismatch | Inspect `file_snapshots` table; ensure snapshot is recorded before the edit |
| WUI auto-scrolls on hover | UI hover handler in WUI is re-anchoring to block top | Track issue #944; pure UI fix, no daemon change needed |
| Archive flow takes 4 keystrokes | No `CMD+C` shortcut implemented in WUI | Track issue #905; one-keystroke archive is a thin wrapper over `BulkArchive` |

## See Also

- [HumanLayer CLI (`hlyr`)](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md) — terminal companion, MCP, thoughts, and `claude init`
- [TypeScript SDK (`@humanlayer/hld-sdk`)](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/README.md) — generated client for the REST + SSE surface
- [Go SDK for Claude Code (`claudecode-go`)](https://github.com/humanlayer/humanlayer/blob/main/claudecode-go/README.md) — embedded Claude Code driver used by the wrapper
- [WUI daemon types](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts) — typed surface the WUI consumes
- Community issues referenced on this page: #956 (bypass-permissions policy), #944 (auto-scroll on hover), #905 (one-keystroke archive)

---

<a id='page-3'></a>

## CodeLayer Desktop (humanlayer-wui)

### Related Pages

Related topics: [System Overview and Architecture](#page-1), [hld Daemon: Sessions, Approvals, MCP, and Storage](#page-2), [hlyr CLI, SDKs, Contracts, and Release Pipeline](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md)
- [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json)
- [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md)
- [README.md](https://github.com/humanlayer/humanlayer/blob/main/README.md)
- [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md)
- [hlyr/src/index.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/index.ts)
- [hld/sdk/typescript/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/README.md)
- [hld/sdk/typescript/package.json](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/package.json)
</details>

# CodeLayer Desktop (humanlayer-wui)

## Overview

CodeLayer Desktop is the open-source Tauri + React front-end for the HumanLayer daemon (`hld`). It is positioned in the [README.md](https://github.com/humanlayer/humanlayer/blob/main/README.md) as a "Superhuman for Claude Code" — a keyboard-first IDE that orchestrates AI coding agents against complex codebases. The application wraps the Claude Code agent runtime, exposes session-level workflows, and pairs every UI with a locally-running daemon that brokers approvals, sessions, and events.

The desktop app lives under the `humanlayer-wui/` directory in the monorepo. Per the [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md), the WUI is built with Tauri (Rust shell) and React (renderer), and is intentionally designed to auto-launch its own daemon in development so the user "starts CodeLayer in development mode" without separate process management.

### Goals

| Goal | Description | Source |
| --- | --- | --- |
| Orchestrate coding agents | Drive one or more Claude Code sessions from a unified UI | [README.md](https://github.com/humanlayer/humanlayer/blob/main/README.md) |
| Keyboard-first workflows | Replace mouse-heavy flows with hotkeys and command palettes | [README.md](https://github.com/humanlayer/humanlayer/blob/main/README.md) |
| Multi-Claude parallel work | Run multiple Claude Code sessions in parallel, including across worktrees | [README.md](https://github.com/humanlayer/humanlayer/blob/main/README.md) |
| Advanced context engineering | Apply battle-tested patterns when scaling agents to large repos | [README.md](https://github.com/humanlayer/humanlayer/blob/main/README.md) |
| Auto-managed daemon | Start/stop the `hld` daemon transparently per git branch | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |

### Scope Boundaries

CodeLayer Desktop is the presentation and orchestration layer. It does not embed the LLM runtime itself; that responsibility is delegated to the Claude Code CLI invoked by the daemon. The WUI is responsible for:

- Rendering the session list, conversation stream, approval dialogs, and settings.
- Subscribing to Server-Sent Events (SSE) from the daemon for real-time updates.
- Providing a debug panel and lifecycle controls for the underlying daemon.
- Bundling and shipping the daemon and the `hlyr` CLI as resources.

---

## High-Level Architecture

The desktop stack has three runtime tiers:

1. **Tauri shell** — native window, file system access, and child process orchestration.
2. **React renderer** — the user-facing SPA that consumes the daemon REST + SSE API.
3. **HumanLayer daemon (`hld`)** — the Go process that mediates between the renderer and Claude Code.

```mermaid
graph TD
  User[Developer] --> Shell[Tauri Shell]
  Shell --> Renderer[React Renderer]
  Renderer -->|REST + SSE| Daemon[hld daemon]
  Daemon -->|stdio / spawn| Claude[Claude Code CLI]
  Daemon -->|SDK calls| HLDClient[@humanlayer/hld-sdk]
  CLI[hlyr CLI] -->|bundled| Shell
  Daemon -.->|per-branch DB| SQLite[(SQLite: daemon-{branch}.db)]
```

Source: [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md), [hld/sdk/typescript/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/README.md).

### Process Topology in Development

In `make codelayer-dev` mode the daemon starts invisibly alongside the app. The WUI performs two important preconditions:

- It expects the daemon binary to already be built (the `make daemon-dev-build` target).
- It copies `daemon-dev.db` to a per-branch database file (e.g. `daemon-feature-foo.db`) so multiple worktrees can coexist.

Source: [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md).

```mermaid
graph LR
  A[make daemon-dev-build] --> B[Built hld binary]
  B --> C[make codelayer-dev]
  C --> D[Tauri launches]
  D --> E[Daemon auto-spawns]
  E --> F[Branch detection]
  F --> G[Copy daemon-dev.db to daemon-{branch}.db]
  G --> H[Bind isolated socket/port]
  H --> I[React renderer connects]
```

Source: [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md).

---

## Technology Stack

The WUI relies on a curated set of front-end libraries to deliver a fast, accessible desktop experience.

| Layer | Library | Notes | Source |
| --- | --- | --- | --- |
| Framework | React 19.1 | Renderer inside Tauri webview | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| Routing | react-router-dom 7.x | App navigation | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| State | Zustand (slice pattern) | Used in demo store; adopted app-wide | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |
| UI primitives | Radix UI (Dialog, Popover, Dropdown, Select, etc.) | Headless, accessible components | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| Editor | Tiptap + lowlight | Rich text and code highlighting | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| Markdown | react-markdown + remark-gfm | Rendering assistant output | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| Hotkeys | react-hotkeys-hook | Keyboard-first command palette | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| Toasts | sonner | Lightweight notifications | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| Analytics | posthog-js | Product analytics | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| SDK | @humanlayer/hld-sdk (file dep) | Typed REST + SSE client for the daemon | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| Build | Tauri + Vite | Desktop packaging and bundling | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |
| SDK generation | OpenAPI Generator (Docker) | Produces typed SDK for the daemon | [hld/sdk/typescript/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/README.md) |

### Package Scripts

The `humanlayer-wui/package.json` defines a compact set of development scripts:

| Script | Purpose | Source |
| --- | --- | --- |
| `dev` | Start Vite dev server | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| `build` | Type-check (`tsc`) and Vite production build | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| `tauri` | Invoke Tauri CLI (dev, build, bundle) | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| `lint` | ESLint over `.ts` and `.tsx` | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| `format` / `format:check` | Prettier write/check | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| `typecheck` | `tsc --noEmit` | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| `check` | `format:check` + `lint` + `typecheck` | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| `test` | Bun test runner | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| `storybook` / `build-storybook` | Component documentation & visual review | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |

---

## Daemon Integration

The WUI is paired with the HumanLayer daemon (`hld`). The daemon exposes a REST API plus Server-Sent Events (SSE) that the renderer subscribes to for live session updates. The TypeScript SDK that wraps the daemon is published as `@humanlayer/hld-sdk` and is consumed directly via a local file dependency in the WUI's `package.json`.

### SDK Surface

The WUI uses the SDK for two main concerns: issuing requests and streaming events.

```mermaid
graph TD
  R[React component] --> H[HLDClient]
  H -->|HTTP/JSON| D[hld REST]
  H -->|SSE| D
  D -->|events| H
  H -->|onMessage / onError| R
```

Source: [hld/sdk/typescript/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/README.md), [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json).

### Example: Creating and Subscribing to a Session

```typescript
import { HLDClient } from '@humanlayer/hld-sdk';

const client = new HLDClient({ port: 7777 });

const session = await client.createSession({
  query: "Help me fix a bug",
  model: "claude-3.5-sonnet",
  workingDir: "/path/to/project"
});

const sessions = await client.listSessions({ leafOnly: true });

const unsubscribe = await client.subscribeToEvents(
  { sessionId: session.sessionId },
  {
    onMessage: (event) => console.log('Event:', event),
    onError: (error) => console.error('Error:', error)
  }
);
```

Source: [hld/sdk/typescript/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/README.md).

### Daemon Lifecycle and Environment Variables

The WUI exposes three environment variables that control daemon behavior at runtime:

| Variable | Default | Effect | Source |
| --- | --- | --- | --- |
| `HUMANLAYER_WUI_AUTOLAUNCH_DAEMON` | (unset, treated as on) | When `false`, the WUI does not auto-spawn the daemon | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |
| `HUMANLAYER_DAEMON_HTTP_PORT` | (default daemon port) | Overrides the port the WUI connects to | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |
| `HUMANLAYER_API_KEY` | (none) | API key for contacting a human reviewer; required for the hosted path | [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md) |

In development:

- The daemon starts invisibly when CodeLayer launches.
- Each git branch gets its own daemon instance.
- The database is copied from `daemon-dev.db` to `daemon-{branch}.db`.
- Sockets and ports are isolated per branch.
- A debug panel (bottom-left settings icon) provides manual lifecycle control when needed.

Source: [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md).

---

## State Management with Zustand Slices

The WUI adopts Zustand's slice pattern for composing state. The demo store under `humanlayer-wui/src/stores/demo/` is documented as a reference implementation of this pattern, and the same composition style is implied for production stores.

### Composition Shape

```typescript
type ComposedDemoStore = SessionSlice & LauncherSlice & ThemeSlice & AppSlice
```

Source: [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md).

### Core Slices

| Slice | File | Responsibility | Source |
| --- | --- | --- | --- |
| `SessionSlice` | `slices/sessionSlice.ts` | Session list, focus state, search, utilities (find, count, exists) | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |
| `LauncherSlice` | `slices/launcherSlice.ts` | Session launcher modal: open/close, command vs search, input, menu nav | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |
| `ThemeSlice` | `slices/themeSlice.ts` | Theme selection, DOM updates, localStorage persistence, dark/light detection | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |
| `AppSlice` | `slices/appSlice.ts` | Connection status, approvals, routing, app-wide workflows (disconnect/reconnect) | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |

### Common Patterns

The README documents two reusable patterns that production slices should follow.

**Conditional state updates** — When updating an entity, also mirror the change to the focused entity if it matches:

```typescript
const updateIfFocused = (id: string, updates: Partial<SessionInfo>) => {
  const { focusedSession, updateSession, setFocusedSession } = store.getState()

  updateSession(id, updates)

  if (focusedSession?.id === id) {
    setFocusedSession({ ...focusedSession, ...updates })
  }
}
```

Source: [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md).

**Workflow actions** — A single action that mutates multiple slices coherently:

```typescript
const completeApproval = (approvalId: string) => {
  const { removeApproval, sessions, updateSession } = store.getState()

  // Remove approval
  removeApproval(approvalId)

  // Update related session
  const session = sessions.find(s => s.status === SessionStatus.WaitingInput)
  if (session) {
    updateSession(session.id, { status: SessionStatus.Running })
  }
}
```

Source: [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md).

### Demo Store Purpose

The demo store is intentionally separate from production state. It is a "comprehensive demo store system for creating synthetic product shots and marketing animations using Zustand's slice pattern architecture." The store generates consistent product demonstrations programmatically, supports storybook-style screenshot capture, and serves as a reference for slice composition.

Source: [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md).

### Troubleshooting

| Issue | Likely Cause | Source |
| --- | --- | --- |
| Theme not applying | DOM unavailable (e.g., running under SSR) | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |
| Animation not looping | `autoPlay` not set to `true` on the animator | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |
| State not updating | Wrong selector used to subscribe to the store | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |
| Tests failing | Missing mocks for `localStorage` and `document` | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |

---

## Development Workflow

The WUI's development workflow centers on Make-driven convenience targets. The `make codelayer-dev` target bootstraps Vite, Tauri, and the bundled daemon in one command.

### Common Targets

| Command | Behavior | Source |
| --- | --- | --- |
| `make daemon-dev-build` | Build the Go daemon so the WUI can auto-spawn it | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |
| `make codelayer-dev` | Build daemon (if needed) and start CodeLayer in dev mode | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |
| `make codelayer-bundle` | Build daemon + `hlyr` CLI for macOS ARM64, copy to Tauri resources, build DMG | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |
| `bun run check` | Format, lint, and type-check the WUI | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |
| `bun test` | Run the Bun test suite | [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json) |

### Per-Branch Daemon Isolation

The WUI automatically detects the current git branch and provisions a per-branch daemon. This matters when a developer works across multiple worktrees simultaneously, since the same global daemon port would otherwise cause collisions.

```mermaid
graph TD
  Start[codelayer-dev launches] --> Detect[Detect current git branch]
  Detect --> Copy[Copy daemon-dev.db to daemon-{branch}.db]
  Copy --> Bind[Bind socket + port for branch]
  Bind --> Connect[Renderer connects to branch daemon]
  Connect --> Work[Work proceeds in isolation]
  Work --> Stop[On exit, daemon terminates]
```

Source: [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md).

### Production Build

`make codelayer-bundle` produces a signed DMG that ships the daemon and the `hlyr` CLI as Tauri resources. The end-user experience is identical to development: the app launches the daemon automatically and the user does not interact with the binaries directly.

Source: [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md).

---

## Nightly Release Channel

The repository publishes a Homebrew cask for nightly builds, distributed as `codelayer-nightly`. The nightly channel is intended for users who want the latest features before they land in stable.

### Installation Methods

| Method | Steps | Source |
| --- | --- | --- |
| Homebrew (recommended) | `brew tap humanlayer/humanlayer` then `brew install --cask codelayer-nightly` | Community context |
| Manual | Download the DMG from the GitHub release page and drag the app to Applications | Community context |

### Caveats

- The nightly channel is explicitly labeled as a nightly build and "may contain unstable features."
- The bundled daemon and CLI versions track the WUI's release.

Source: Community context (latest release announcement).

---

## CLI Interop (`hlyr`)

The `hlyr` (or `humanlayer`) CLI is bundled with the WUI and shares the same daemon contract. When `humanlayer` is invoked in a terminal, the `hlyr/src/index.ts` entry point can optionally launch the desktop app rather than executing CLI commands.

### Launching the App from the CLI

```typescript
// hlyr/src/index.ts (excerpt)
if (shouldLaunchApp(invocationName, hasArgs)) {
  const appPath = getAppPath(invocationName)
  if (appPath) {
    launchApp(appPath)
    process.exit(0)
  } else {
    const appName = invocationName === 'codelayer-nightly' ? 'CodeLayer-Nightly' : 'CodeLayer'
    console.error(`${appName} app not found. Please install it first:`)
    console.error(
      `    brew install --cask humanlayer/humanlayer/${invocationName === 'codelayer-nightly'
    ...
```

Source: [hlyr/src/index.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/index.ts).

The CLI exposes a unified surface that pairs with the desktop app:

- `humanlayer contact_human` — direct human contact from terminal or scripts.
- `humanlayer claude init` — initialize `.claude/` directories (commands, agents, settings).
- `humanlayer thoughts ...` — manage developer notes with profile-aware repositories.
- `humanlayer join-waitlist` — register for early access.

Source: [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md), [hlyr/src/index.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/index.ts).

### Configuration Patterns

The CLI supports the same channels the desktop app can route approvals to:

```bash
# Environment variables
export HUMANLAYER_API_KEY=...
export HUMANLAYER_SLACK_CHANNEL=C08G5C3V552
export HUMANLAYER_EMAIL_ADDRESS=human@example.com

# File-based
echo '{
  "channel": { "slack": { "channel_or_user_id": "C08G5C3V552" } }
}' > .hlyr.json
humanlayer contact_human --message "Review this pull request" --config-file .hlyr.json
```

Source: [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md).

---

## Community-Driven Concerns and Workarounds

A handful of open issues and feedback items directly shape how the WUI is used in practice. They are summarized here so that readers can find relevant context for the most common friction points.

### Issue #956 — Disallow "Disable bypass permissions"

> "Our company would love to use HumanLayer, but because of security policies we cannot allow the option of checking 'Disable bypass permissions check'." — Community context

This is a security-policy request from enterprise teams: the current UI exposes an option to disable the Claude Code permission bypass, but some compliance regimes require that the bypass never be disabled at all. The WUI does not yet gate this option behind an admin policy. Workarounds until this is addressed include:

- Distributing a hardened settings.json that omits the bypass option.
- Reviewing Claude Code invocation flags passed by the daemon in the bundled `hld` build.

Source: Community context, issue #956.

### Issue #944 — Auto-scroll When Hovering Feels Weird

> "When I am scrolling to see the different steps/messages, I am auto scrolled to the top of each block and it feels awkward." — Community context

The session detail view auto-scrolls to the top of a step when the user hovers it. This conflicts with the user's intent to read linearly through a long transcript. Suggested workarounds:

- Use keyboard navigation instead of the mouse.
- Disable hover effects via a user stylesheet.

Source: Community context, issue #944.

### Issue #905 — `CMD+C` Shortcut to Quickly Archive and Recreate

> "In Claude Code, clearing context is simple: `/clear` + ENTER. In CodeLayer, I need to: Press E, Press C, Write a name, Update directory..." — Community context

Users want a single-key shortcut (analogous to Claude Code's `/clear`) that archives the current session and recreates one. Today the flow requires four discrete steps. This is a usability gap that is tracked in the issue; readers implementing session flows should account for the multi-step launcher path.

Source: Community context, issue #905.

---

## Quality Gates and Continuous Integration

The `check` script in `humanlayer-wui/package.json` is the canonical pre-merge gate:

```bash
bun run check   # format:check && lint && typecheck
bun test        # bun test runner
```

The Tauri build is invoked via the `tauri` script. Storybook runs on port 6006 and is the recommended way to review component changes in isolation before bundling.

Source: [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json).

### SDK Regeneration

The TypeScript SDK that the WUI consumes is generated from the daemon's OpenAPI specification. When the daemon's API changes:

```bash
cd hld/sdk/typescript
bun install
bun run generate   # uses Docker, no Java required
bun run build
```

The WUI picks up the regenerated SDK through the `file:../hld/sdk/typescript` dependency declared in [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json).

Source: [hld/sdk/typescript/README.md](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/README.md).

---

## Failure Modes and Common Pitfalls

| Failure Mode | Description | Mitigation | Source |
| --- | --- | --- | --- |
| Daemon port collision | A second worktree tries to bind the same port | Rely on per-branch daemon isolation, or override `HUMANLAYER_DAEMON_HTTP_PORT` | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |
| Stale daemon DB | A schema change ships but `daemon-dev.db` is left over | Delete the per-branch DB and let the WUI re-seed it | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |
| Auto-launch disabled silently | `HUMANLAYER_WUI_AUTOLAUNCH_DAEMON=false` set in shell | Unset the variable or launch the daemon by hand and use the debug panel | [humanlayer-wui/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/README.md) |
| Auto-scroll disturbance | Hovering a step jumps the viewport | Use keyboard navigation; track issue #944 for a fix | Community context, #944 |
| "Disable bypass permissions" exposed | Compliance regime forbids the option | Apply a hardened `settings.json`; track issue #956 | Community context, #956 |
| Multi-step archive flow | No single-key shortcut to recreate a session | Track issue #905 for the proposed `CMD+C` shortcut | Community context, #905 |
| Theme not applying under SSR | The DOM is not available when theme slice runs | Ensure the theme slice is invoked only in the browser | [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md) |

---

## Summary

CodeLayer Desktop is the visual control plane for the HumanLayer platform. Built on Tauri + React and wired to a Go daemon via a generated TypeScript SDK, it offers:

- A keyboard-first, multi-session IDE for orchestrating Claude Code agents.
- Automatic per-branch daemon lifecycle management.
- A composable Zustand state architecture demonstrated by the `demo` store.
- A bundled CLI (`hlyr`) that can also launch the desktop app.

The community-flagged friction points — security gating of the bypass-permissions option, hover-driven auto-scroll, and the absence of a one-step session reset — are all areas of active feedback that shape the next iterations of the WUI.

---

## See Also

- [HumanLayer Repository Overview](./Home.md) — top-level product context.
- [HumanLayer CLI (hlyr)](./HumanLayer-CLI.md) — terminal companion and bundled CLI.
- [HumanLayer Daemon TypeScript SDK](./HLD-TypeScript-SDK.md) — generated REST + SSE client.
- [Linear CLI](./Linear-CLI.md) — auxiliary tool used alongside the WUI for ticket tracking.
- [Community Issues Dashboard](https://github.com/humanlayer/humanlayer/issues) — track open feedback (#905, #944, #956 and others).

---

<a id='page-4'></a>

## hlyr CLI, SDKs, Contracts, and Release Pipeline

### Related Pages

Related topics: [System Overview and Architecture](#page-1), [hld Daemon: Sessions, Approvals, MCP, and Storage](#page-2), [CodeLayer Desktop (humanlayer-wui)](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md)
- [hlyr/src/thoughtsConfig.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/thoughtsConfig.ts)
- [hlyr/src/commands/thoughts/init.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/commands/thoughts/init.ts)
- [hack/linear/README.md](https://github.com/humanlayer/humanlayer/blob/main/hack/linear/README.md)
- [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json)
- [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts)
- [hld/sdk/typescript/src/generated/models/MCPServer.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/MCPServer.ts)
- [hld/sdk/typescript/src/generated/models/Approval.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/Approval.ts)
- [claudecode-go/README.md](https://github.com/humanlayer/humanlayer/blob/main/claudecode-go/README.md)
- [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md)
- [humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts)
- [humanlayer-wui/src-tauri/src/lib.rs](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src-tauri/src/lib.rs)
</details>

# hlyr CLI, SDKs, Contracts, and Release Pipeline

The HumanLayer project is composed of several interconnected surfaces: a Node-based command-line interface (`hlyr`), a Go SDK for Claude Code, an auto-generated TypeScript SDK, a desktop/web UI ("CodeLayer") built on Tauri + React, and a release pipeline that produces nightly, dev, and production builds. This page documents the public contracts, subcommands, configuration surfaces, and release mechanics that hold those surfaces together.

## 1. Repository Surface Map

The monorepo is organized into a small number of high-level components. The CLI (`hlyr`) is the connective tissue; the SDKs (TypeScript and Go) consume the same API surface; and the WUI (CodeLayer) is the desktop client.

```mermaid
graph TD
    A[hlyr CLI<br/>Node/TypeScript] --> B[hld Daemon<br/>HTTP + SSE]
    A --> C[Claude Code SDK<br/>Approval flow]
    B --> D[TypeScript SDK<br/>@humanlayer/hld-sdk]
    B --> E[Go SDK<br/>claudecode-go]
    F[CodeLayer WUI<br/>Tauri + React] --> B
    F --> G[claudecode-go<br/>embedded process]
    H[Thoughts system<br/>hlyr thoughts *] --> I[Per-repo + Global notes]
    A --> H
    J[hack/linear CLI] --> K[Linear API]
    L[Release Pipeline] --> A
    L --> F
```

| Component | Path | Purpose |
|---|---|---|
| `hlyr` CLI | `hlyr/` | Terminal entry point, MCP servers, thoughts management |
| TypeScript SDK | `hld/sdk/typescript/` | Generated client for the `hld` daemon |
| Go SDK | `claudecode-go/` | Programmatic Claude Code subprocess control |
| CodeLayer WUI | `humanlayer-wui/` | Desktop client (Tauri + React + Zustand) |
| Linear CLI helper | `hack/linear/` | Linear issue tracking from terminal |
| Thoughts system | `hlyr/src/commands/thoughts/`, `hlyr/src/thoughtsConfig.ts` | Per-repo + global notes with symlinks |

Source: [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md), [claudecode-go/README.md](https://github.com/humanlayer/humanlayer/blob/main/claudecode-go/README.md), [hack/linear/README.md](https://github.com/humanlayer/humanlayer/blob/main/hack/linear/README.md).

## 2. The `hlyr` CLI

`hlyr` is the developer-facing CLI. It is documented in [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md) and is designed to be runnable directly via `npx humanlayer ...`. It exposes four top-level command groups: `contact_human`, `mcp`, `thoughts`, and `claude`.

### 2.1 Command Groups

| Command | Subcommands | Notes |
|---|---|---|
| `contact_human` | `-m`, `--message`, `--slack-channel`, `--email`, `--config-file` | One-shot human-in-the-loop ping |
| `mcp` | `serve`, `claude_approvals`, `inspector serve`, `inspector claude_approvals` | Model Context Protocol servers |
| `thoughts` | `init`, `sync`, `status`, `config`, `profile {create,list,show,delete}` | Multi-profile notes system |
| `claude` | `init`, `init --all`, `init --force` | Scaffolds `.claude/` directory with commands/agents/settings |

Source: [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md).

### 2.2 Contact Channel Resolution

The CLI resolves the contact destination through a layered configuration system. The order of precedence (most explicit wins) is:

1. CLI flags (e.g. `--slack-channel C08G5C3V552`)
2. Environment variables (`HUMANLAYER_SLACK_CHANNEL`, `HUMANLAYER_EMAIL_ADDRESS`)
3. Config file passed via `--config-file` (default schema: `.hlyr.json`)
4. Implicit fallback to the web UI

Example config-file shape:

```json
{
  "channel": {
    "slack": {
      "channel_or_user_id": "C08G5C3V552"
    }
  }
}
```

```bash
humanlayer contact_human --message "Review this pull request" --config-file .hlyr.json
```

Source: [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md).

> **Note from the community:** Customers running in regulated environments (see issue #956) have asked for a way to disallow the "Disable bypass permissions" option. The CLI/MCP approval surface is the integration point where such a policy would be enforced, since the `claude_approvals` MCP server is what consults humans before dangerous actions are taken. As of this writing, the README does not describe a hard-deny policy mode; teams that need it must either run the daemon in an isolated environment or filter inbound approval requests upstream.

### 2.3 MCP Server Surface

`hlyr mcp` exposes the daemon's human-in-the-loop and Claude Code approval capabilities to MCP-compatible clients (Claude Desktop, Claude Code SDK, etc.).

```bash
# Contact-human MCP server
humanlayer mcp serve

# Claude Code SDK approval integration
humanlayer mcp claude_approvals

# Debug with the MCP inspector
humanlayer mcp inspector serve
humanlayer mcp inspector claude_approvals
```

A typical `mcp-config.json` for Claude Code:

```json
{
  "mcpServers": {
    "approvals": {
      "command": "npx",
      "args": ["-y", "humanlayer", "mcp", "claude_approvals"],
      "env": {
        "HUMANLAYER_API_KEY": "<YOUR_API_KEY>"
      }
    }
  }
}
```

Source: [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md).

## 3. The Thoughts System

The `hlyr thoughts` command group implements a per-repository + global notes system that is automatically synced via git hooks. It is the CLI's "second brain" surface, and it is the most configuration-heavy subsystem.

### 3.1 Profile Model

The thoughts system supports **named profiles**, each pointing at its own thoughts repository. This lets a single user maintain separate notes trees for personal projects, multiple clients, or different worktrees of the same repo.

```mermaid
graph TD
    A[hlyr thoughts] --> B[Default profile]
    A --> C[Profile: personal]
    A --> D[Profile: client-acme]
    B --> E[~/thoughts-default]
    C --> F[~/thoughts-personal]
    D --> G[~/thoughts-acme]
    H[Repo: project-x] --> C
    I[Repo: project-y] --> D
```

| Command | Purpose |
|---|---|
| `thoughts init` | Initialize thoughts for the current repo (default profile) |
| `thoughts init --profile <name>` | Initialize using a specific profile |
| `thoughts profile create <name>` | Create a new profile with optional `--repo`, `--repos-dir`, `--global-dir` |
| `thoughts profile list [--json]` | List all profiles |
| `thoughts profile show <name>` | Show one profile |
| `thoughts profile delete <name> [--force]` | Delete a profile |
| `thoughts sync -m "msg"` | Commit and push notes |
| `thoughts status` | Show active profile + sync status |
| `thoughts config` | Show full resolved configuration |

Source: [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md), [hlyr/src/commands/thoughts/init.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/commands/thoughts/init.ts).

### 3.2 Directory Layout

The directory structure created by `thoughts init` is:

```
<thoughtsRepo>/<reposDir>/<repoName>/<user>/   # repo-specific personal notes
<thoughtsRepo>/<reposDir>/<repoName>/shared/   # repo-specific team notes
<thoughtsRepo>/<globalDir>/<user>/             # cross-repo personal notes
<thoughtsRepo>/<globalDir>/shared/             # cross-repo team notes
<thoughtsRepo>/<reposDir>/<repoName>/searchable/   # hard links for search tools
```

A `README.md` is auto-generated in both the repo-specific and global directories. The `searchable/` directory uses **hard links** to originals, so editing either path mutates both — but the docs explicitly direct users to reference files by their **canonical** path (e.g. `thoughts/<user>/todo.md`), not via `searchable/`.

Source: [hlyr/src/commands/thoughts/init.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/commands/thoughts/init.ts).

### 3.3 Symlink and Overload Behavior

`hlyr/src/thoughtsConfig.ts` defines overloaded signatures for both `createThoughtsDirectoryStructure` and `updateSymlinksForNewUsers`, supporting both a legacy positional argument shape and a newer `(config, repoName, user)` shape. This dual API is the in-tree evidence of a "backward compatible" refactor — the README claims the same.

| Function | Legacy Signature | New Signature |
|---|---|---|
| `createThoughtsDirectoryStructure` | `(thoughtsRepo, reposDir, globalDir, repoName, user)` | `(config: ResolvedProfileConfig, repoName, user)` |
| `updateSymlinksForNewUsers` | `(currentRepoPath, thoughtsRepo, reposDir, repoName, currentUser)` | `(currentRepoPath, config, repoName, currentUser)` |

Source: [hlyr/src/thoughtsConfig.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/thoughtsConfig.ts).

## 4. SDKs and Contracts

The two first-party SDKs — TypeScript and Go — share a common conceptual model: sessions, approvals, MCP servers, and events. The TypeScript SDK is auto-generated from the daemon's OpenAPI spec; the Go SDK is a hand-written wrapper around the Claude Code subprocess.

### 4.1 TypeScript SDK (`@humanlayer/hld-sdk`)

The TS SDK is published as `@humanlayer/hld-sdk` (via a `file:../hld/sdk/typescript` path in the WUI's `package.json`). It exposes a single `HLDClient` class with multiple sub-clients.

#### 4.1.1 Client Surface

| Sub-client | Backing API | Source |
|---|---|---|
| `sessionsApi` | `SessionsApi` | [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts) |
| `approvalsApi` | `ApprovalsApi` | same |
| `settingsApi` | `SettingsApi` | same |
| `filesApi` | `FilesApi` | same |
| `agentsApi` | `AgentsApi` | same |
| `systemApi` | `SystemApi` | same |

#### 4.1.2 Constructor Options

```typescript
new HLDClient({
  baseUrl?: string,
  port?: number,
  headers?: Record<string, string>,
  onFetchError?: (error: Error, ctx: { url: string; method?: string }) => void,
})
```

#### 4.1.3 SSE Event Handling

`HLDClient` maintains a `Map<string, EventSourceLike>` of Server-Sent Events connections, abstracted through a unified `EventSourceLike` interface so it works in both browser and polyfill environments. Handlers are passed in via the `SSEEventHandlers` interface:

```typescript
interface SSEEventHandlers {
  onMessage?: (event: any) => void
  onError?: (error: Error) => void
  onConnect?: () => void
  onDisconnect?: () => void
}
```

Source: [hld/sdk/typescript/src/client.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/client.ts).

#### 4.1.4 Generated Models

The SDK ships a large set of generated models. Two of the most central are `Approval` and `MCPServer`.

**`Approval` contract (selected fields):**

| Field | Type | Notes |
|---|---|---|
| `id` | `string` | Required |
| `runId` | `string` | Required |
| `sessionId` | `string` | Required |
| `status` | `ApprovalStatus` | Required |
| `createdAt` | `Date` | Required |
| `respondedAt` | `Date` | Optional |
| `toolName` | `string` | Required |
| `toolInput` | `{ [key: string]: any }` | Required |
| `comment` | `string` | Optional |

Source: [hld/sdk/typescript/src/generated/models/Approval.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/Approval.ts).

**`MCPServer` contract (selected fields):**

| Field | Type | Notes |
|---|---|---|
| `type` | `string` | e.g. `stdio`, `http` |
| `command` | `string` | For stdio servers |
| `args` | `Array<string>` | For stdio servers |
| `env` | `{ [key: string]: string }` | For stdio servers |
| `url` | `string` | For HTTP servers |
| `headers` | `{ [key: string]: string }` | For HTTP servers |

Source: [hld/sdk/typescript/src/generated/models/MCPServer.ts](https://github.com/humanlayer/humanlayer/blob/main/hld/sdk/typescript/src/generated/models/MCPServer.ts).

### 4.2 Go SDK (`claudecode-go`)

The Go SDK is a programmatic wrapper around the Claude Code CLI subprocess. It is intentionally minimal: it spawns Claude, captures its output, and exposes typed result/error shapes.

#### 4.2.1 Configuration

```go
type SessionConfig struct {
    // Core
    Query     string
    SessionID string // Resume existing session

    // Model
    Model Model // ModelOpus, ModelSonnet, or ModelHaiku

    // Output
    OutputFormat OutputFormat

    // MCP
    MCPConfig            *MCPConfig
    PermissionPromptTool string

    // Control
    MaxTurns           int
    WorkingDir         string
    SystemPrompt       string
    AppendSystemPrompt string
    AllowedTools       []string
    DisallowedTools    []string
    Verbose            bool
}
```

#### 4.2.2 Output Formats

| Format | Description |
|---|---|
| `OutputText` | Plain text output (default) |
| `OutputJSON` | Structured JSON with metadata |
| `OutputStreamJSON` | Real-time streaming JSON events |

#### 4.2.3 Error Handling

```go
result, err := client.LaunchAndWait(config)
if err != nil {
    // Handle launch/execution errors
    log.Fatal(err)
}
if result.IsError {
    // Handle Claude-reported errors
    fmt.Printf("Error: %s\n", result.Error)
}
```

Source: [claudecode-go/README.md](https://github.com/humanlayer/humanlayer/blob/main/claudecode-go/README.md).

### 4.3 Daemon Event Contracts (WUI side)

The WUI's `humanlayer-wui/src/lib/daemon/types.ts` defines the event payloads exchanged with the daemon. The shape of these events is what the SDKs and WUI agree on as the "wire contract".

| Event | Key fields | Purpose |
|---|---|---|
| `NewApprovalEventData` | `approval_id`, `session_id`, `tool_name` | A new approval has been requested |
| `ApprovalResolvedEventData` | `approval_id`, `session_id`, `decision` | An approval was answered |
| `SessionStatusChangedEventData` | `session_id`, `old_status`, `new_status` | Session lifecycle transitions |
| `SessionSettingsChangedEventData` | `session_id`, `auto_accept_edits`, `dangerously_skip_permissions`, `dangerously_skip_permissions_timeout_ms`, `reason`, `expired_at` | Dangerous skip settings changed |

The `SessionSettingsChangeReason` constant currently exposes a single value — `EXPIRED: 'expired'` — used when the "dangerous skip permissions" window times out. This is the precise mechanism referenced by the community request in issue #956: today the system can *expire* the dangerous-skip window, but it does not yet support a *policy-deny* mode where the option is never offered.

Source: [humanlayer-wui/src/lib/daemon/types.ts](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/lib/daemon/types.ts).

## 5. CodeLayer Desktop App

The `humanlayer-wui` package is a Tauri 2 desktop application wrapping a React 19 frontend. It is the surface that consumes the SDKs at runtime.

### 5.1 Stack

| Layer | Technology |
|---|---|
| Shell | Tauri 2 (`@tauri-apps/cli ^2`) |
| UI | React 19, Vite, Tailwind 4 |
| State | Zustand 5 (with per-slice stores under `src/stores/`) |
| Editor | Tiptap 3 (with `lowlight` for code highlighting) |
| Routing | `react-router-dom 7.6.3` |
| Telemetry | `posthog-js 1.279.3` |
| Search/keyboard | `cmdk`, `react-hotkeys-hook`, `fuzzy` |
| Test | `bun test` (Bun runtime) |
| Storybook | v9.1.5 |

Source: [humanlayer-wui/package.json](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/package.json).

### 5.2 State Management

The demo-store README describes the canonical pattern: keep slices focused, use TypeScript strictly, test first, document sequences, and handle edge cases. Two patterns are documented as "common":

```typescript
// Conditional state updates
const updateIfFocused = (id: string, updates: Partial<SessionInfo>) => {
  const { focusedSession, updateSession, setFocusedSession } = store.getState()
  updateSession(id, updates)
  if (focusedSession?.id === id) {
    setFocusedSession({ ...focusedSession, ...updates })
  }
}

// Workflow actions
const completeApproval = (approvalId: string) => {
  const { removeApproval, sessions, updateSession } = store.getState()
  removeApproval(approvalId)
  const session = sessions.find(s => s.status === SessionStatus.WaitingInput)
  if (session) updateSession(session.id, { status: SessionStatus.Running })
}
```

Source: [humanlayer-wui/src/stores/demo/README.md](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src/stores/demo/README.md).

## 6. Release Pipeline

CodeLayer ships three release tracks that share the same codebase but use different identity, store paths, and update channels. The orchestration lives in the Tauri shell.

### 6.1 Store Path Resolution

The Rust helper `get_store_path` in [humanlayer-wui/src-tauri/src/lib.rs](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src-tauri/src/lib.rs) is the single source of truth for which on-disk store file a given build writes to:

```rust
fn get_store_path(is_dev: bool, branch_id: Option<&str>, is_nightly: bool) -> PathBuf {
    let home = dirs::home_dir().expect("Failed to get home directory");
    let humanlayer_dir = home.join(".humanlayer");
    if is_dev {
        if let Some(branch) = branch_id {
            humanlayer_dir.join(format!("codelayer-{branch}.json"))
        } else {
            humanlayer_dir.join("codelayer-dev.json")
        }
    } else if is_nightly {
        humanlayer_dir.join("codelayer-nightly.json")
    } else {
        humanlayer_dir.join("codelayer.json")
    }
}
```

The branch identifier is derived from the environment, with the ticket-id slugging logic visible in the surrounding code (sanitizing characters and falling back to the literal branch name, or `"production"` / `"dev"` if no branch is detectable).

### 6.2 Build Matrix

| Build | Identifier contains `"nightly"`? | Store file | Use case |
|---|---|---|---|
| Production | No | `~/.humanlayer/codelayer.json` | Stable releases |
| Nightly | Yes | `~/.humanlayer/codelayer-nightly.json` | Cutting-edge previews |
| Dev (default) | No + `is_dev=true` | `~/.humanlayer/codelayer-dev.json` | Local development |
| Dev (branch) | No + `is_dev=true` + `branch_id=Some(...)` | `~/.humanlayer/codelayer-<branch>.json` | Worktree-isolated dev |

Source: [humanlayer-wui/src-tauri/src/lib.rs](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src-tauri/src/lib.rs).

### 6.3 Daemon Lifecycle

The `start_daemon` Tauri command in `lib.rs` is the entry point for spinning up the local `hld` daemon from the desktop app. It takes `is_dev` and an optional `branch_override`, starts the daemon via the `DaemonManager`, then writes the resolved `DaemonInfo` (including `branch_id`) to the appropriate store path.

```rust
#[tauri::command]
async fn start_daemon(
    app_handle: tauri::AppHandle,
    daemon_manager: State<'_, DaemonManager>,
    is_dev: bool,
    branch_override: Option<String>,
) -> Result<DaemonInfo, String> { /* ... */ }
```

Source: [humanlayer-wui/src-tauri/src/lib.rs](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src-tauri/src/lib.rs).

### 6.4 Nightly Distribution

The latest nightly artifact described in the community context is `codelayer-0.1.0-nightly-20260227142452`, distributed via two channels:

- **Homebrew (recommended):**
  ```bash
  brew tap humanlayer/humanlayer
  brew install --cask codelayer-nightly
  ```
- **Manual:** Download the DMG, open it, and drag to Applications.

Each nightly build is explicitly flagged as potentially containing unstable features.

### 6.5 Release Flow Diagram

```mermaid
graph TD
    A[Commit to main] --> B[CI Build]
    B --> C{Channel}
    C -->|production tag| D[codelayer.json]
    C -->|nightly schedule| E[codelayer-nightly.json]
    F[Local dev] --> G{is_dev?}
    G -->|yes, branch X| H[codelayer-X.json]
    G -->|yes, no branch| I[codelayer-dev.json]
    G -->|no| J[uses release store]
    D --> K[User's ~/.humanlayer/]
    E --> K
    H --> K
    I --> K
    J --> K
```

## 7. `hack/linear` — Companion CLI

`hack/linear` is a small companion CLI for Linear issue tracking. It is shipped alongside the rest of the repo as a developer utility, and it follows the same "ship a thin shell wrapper + a TypeScript implementation" pattern as `hlyr`.

| Subcommand | Behavior |
|---|---|
| `list-issues` | List active assigned issues (excludes done/canceled) |
| `get-issue [ENG-XXXX]` | Show an issue; auto-detects ID from current git branch |
| `add-comment "<msg>"` | Adds a comment; auto-detects issue from branch |
| `fetch-images ENG-XXXX` | Downloads issue images to the local thoughts dir |

Shell completions are provided for fish, zsh, and bash. The recommended `CLAUDE.md` snippet is:

```md
## Linear
When asked to fetch a Linear ticket, use the globally installed Linear CLI: `linear get-issue ENG-XXXX > thoughts/shared/tickets/eng-XXXX.md`
```

Source: [hack/linear/README.md](https://github.com/humanlayer/humanlayer/blob/main/hack/linear/README.md).

## 8. Cross-Cutting Concerns

### 8.1 Authentication and API Keys

`HUMANLAYER_API_KEY` is the single credential required across the CLI, the MCP servers, and the SDK consumers. The CLI also reads channel-specific variables (`HUMANLAYER_SLACK_CHANNEL`, `HUMANLAYER_EMAIL_ADDRESS`) but those are routing, not authentication.

Source: [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md).

### 8.2 MCP and Approval Flow

The end-to-end approval flow is the most security-sensitive path in the system. It is implemented as a relay between the Claude Code subprocess, the local `hld` daemon, the `hlyr mcp claude_approvals` MCP server, and (optionally) a human in Slack/email/web.

```mermaid
graph LR
    A[Claude Code subprocess] --> B[hld daemon]
    B --> C[hlyr mcp claude_approvals]
    C --> D{Contact channel}
    D -->|Slack| E[Human in Slack]
    D -->|Email| F[Human in Email]
    D -->|Web UI| G[Human in WUI]
    E --> B
    F --> B
    G --> B
    B --> A
```

This is the surface that community issue #956 wants hardened: enterprise customers need a way to forbid the "Disable bypass permissions" option entirely, so that the `dangerously_skip_permissions` flow can never be enabled regardless of who clicks what in the UI. As of the source files reviewed, the SDK and daemon contract distinguish only between "enabled" and "expired" — they do not yet express a "policy-denied" state.

### 8.3 Common Failure Modes

| Symptom | Likely cause | Reference |
|---|---|---|
| `hlyr` does not contact a channel | Missing or misnamed env var; config file not passed via `--config-file` | [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md) |
| `claude init` exits with no action | Non-TTY environment without `--all` | [hlyr/README.md](https://github.com/humanlayer/humanlayer/blob/main/hlyr/README.md) |
| Thoughts hard links appear stale | Forgot to run `thoughts sync`; remember `searchable/` is just a hard-link mirror | [hlyr/src/commands/thoughts/init.ts](https://github.com/humanlayer/humanlayer/blob/main/hlyr/src/commands/thoughts/init.ts) |
| Linear CLI hangs on no API key | Set `LINEAR_API_KEY`; help/completion work without it | [hack/linear/README.md](https://github.com/humanlayer/humanlayer/blob/main/hack/linear/README.md) |
| Auto-scroll feels "weird" in CodeLayer | Issue #944 — UI feedback under review | Community context |
| Archiving a session is multi-step | Issue #905 — community wants `CMD+C` shortcut equivalent to Claude Code's `/clear` | Community context |
| Dev store conflicts across worktrees | Each branch now has its own `codelayer-<branch>.json` automatically | [humanlayer-wui/src-tauri/src/lib.rs](https://github.com/humanlayer/humanlayer/blob/main/humanlayer-wui/src-tauri/src/lib.rs) |

## See Also

- [hld Daemon](hld-daemon.html) — the HTTP/SSE service that all surfaces ultimately talk to
- [CodeLayer WUI Architecture](codelayer-wui-architecture.html) — Tauri + React + Zustand deep dive
- [MCP Approval Protocol](mcp-approval-protocol.html) — contract details for `claude_approvals`
- [Thoughts System Deep Dive](thoughts-system.html) — profile, symlink, and hard-link semantics
- [Claude Code SDK Reference](claudecode-go.html) — Go SDK for subprocess control

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: humanlayer/humanlayer

Summary: Found 11 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Installation risk - Installation risk requires verification.

## 1. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/humanlayer/humanlayer/issues/981

## 2. Installation risk - Installation risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/humanlayer/humanlayer/issues/994

## 3. Configuration risk - Configuration risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.host_targets | github_repo:838542536 | https://github.com/humanlayer/humanlayer

## 4. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | github_repo:838542536 | https://github.com/humanlayer/humanlayer

## 5. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | github_repo:838542536 | https://github.com/humanlayer/humanlayer

## 6. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | github_repo:838542536 | https://github.com/humanlayer/humanlayer

## 7. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | github_repo:838542536 | https://github.com/humanlayer/humanlayer

## 8. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/humanlayer/humanlayer/issues/959

## 9. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/humanlayer/humanlayer/issues/983

## 10. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | github_repo:838542536 | https://github.com/humanlayer/humanlayer

## 11. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | github_repo:838542536 | https://github.com/humanlayer/humanlayer

<!-- canonical_name: humanlayer/humanlayer; human_manual_source: deepwiki_human_wiki -->
