Sign inSign up

docker/hsp:claude-kit-latest

Manifest digest

sha256:baf6b637d37fb2c0cea2c8c2d50392b57302d5b7a3eb6e36dd4308921f585b13

Last pushed

4 days by docker

Type

Sandbox Kit

Manifest digest

sha256:baf6b637d37fb2c0cea2c8c2d50392b57302d5b7a3eb6e36dd4308921f585b13

yaml
schemaVersion: "2"
kind: sandbox
name: claude-hsp-kit
displayName: Claude Code (HSP Steering)
description: 'HSP-capable variant of the built-in `claude` sandbox kit -- the same `claude` CLI (stock `--dangerously-skip-permissions` entrypoint, no fork, no research-preview flags, pinned to 2.1.287), plus an organization-managed Claude Code *mod* (this repo''s `claude/mod`, loaded from a root-owned directory marketplace via managed settings) that runs a child shim (this repo''s `claude/src/shim.ts`) exposing the Harness Steering Protocol (hsp/1): the shim serves `$HSP_SOCKET`, and hands each accepted steer back to the mod, which injects it into the conversation (`$.session.append` mid-turn, `$.prompt.submit` when idle). Built to test/demo the Agentic Platform''s custom-kit-only HSP capability (APT-1293/APT-1382). Everything below this point through `agentInstructions` is copied verbatim from the built-in `claude` kit spec (docker/sandboxes, sandboxlib/agentkits/agents/claude/spec.yaml) -- see this repo''s claude/README.md ("Claude Code adapter and sandbox kit: claude-hsp-kit"), for the diff summary; the only changes are this description, `sandbox.image`, `sandbox.entrypoint`, `name`, `displayName`, and the `extraKnownMarketplaces`/`enabledPlugins`/`appendPlugins` keys baked into `/etc/claude-code/managed-settings.json` at image build time (claude/kit/Dockerfile). Note: the mod loads in every session launched from this image, but is a no-op unless the platform set `$HSP_SOCKET` (HSP spec 4.2). Unlike the previous settings-hook design there is no `crossSessionInbound` key any more: nothing writes to Claude Code''s cross-session inbox socket.'
sandbox:
    image: docker.io/docker/hsp:claude-base-image-latest
    entrypoint:
        - claude-hsp
agentInstructions:
    filename: CLAUDE.md
    content: |
        ## Claude Code: Environment Persistence

        The `CLAUDE_ENV_FILE` environment variable is set to `/etc/sandbox-persistent.sh`.

        According to [Claude Code Documentation](https://code.claude.com/docs/en/settings#bash-tool-behavior):

        > **CLAUDE_ENV_FILE**
        >
        > If set, this file will be sourced before each Bash command execution. This allows environment variables to persist across multiple Bash tool invocations.

        This means `/etc/sandbox-persistent.sh` is sourced before every Bash tool call you make. Any `export` statements you append to this file will be available in all subsequent commands without needing a login shell.

        ### Shell Completions Must NOT Be in CLAUDE_ENV_FILE

        **NEVER add shell completion scripts to `/etc/sandbox-persistent.sh`.**

        `CLAUDE_ENV_FILE` is sourced **before every single bash command execution**, not just during shell initialization. Completion scripts rely on special variables (`COMP_WORDS`, `COMP_CWORD`, `COMPREPLY`) that only exist during tab-completion contexts.

        #### WRONG - Will Break Bash

        ```bash
        # DO NOT ADD THESE TO /etc/sandbox-persistent.sh
        [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
        [[ -s "$SDKMAN_DIR/etc/bash_completion.sh" ]] && source "$SDKMAN_DIR/etc/bash_completion.sh"
        ```

        #### CORRECT - Only Load Core Functionality

        ```bash
        # ONLY add the main initialization scripts
        export NVM_DIR="$HOME/.nvm"
        [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

        export SDKMAN_DIR="$HOME/.sdkman"
        [[ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]] && source "$SDKMAN_DIR/bin/sdkman-init.sh"
        ```

        #### Symptoms of Broken Shell

        When completion scripts are incorrectly added:
        - All bash commands return no output (silent failure)
        - `echo`, `pwd`, and other basic commands produce no results
        - The bash tool becomes completely unusable

        #### Solution

        If you accidentally added completion scripts and broke the shell:
        1. Remove the completion line(s) from `/etc/sandbox-persistent.sh`
        2. Exit and restart the Claude Code session
        3. Verify with `echo "test"` that bash works again
permissions:
    network:
        allow:
            - api.anthropic.com
            - console.anthropic.com
            - claude.ai
            - mcp-proxy.anthropic.com
            - platform.claude.com
            - downloads.claude.ai
            - claude.com
            - code.claude.com
            - bridge.claudeusercontent.com
volumes:
    - path: /home/agent/.claude/projects
      size: 2g
    - path: /home/agent/.claude/sessions
      size: 512m
    - path: /home/agent/.claude/todos
      size: 512m
    - path: /home/agent/.claude/shell-snapshots
      size: 512m
    - path: /home/agent/.claude/statsig
      size: 512m
credentials:
    - service: anthropic
      apiKey:
        name: ANTHROPIC_API_KEY
        inject:
            - domain: api.anthropic.com
              header: x-api-key
              format: '%s'
            - domain: console.anthropic.com
              header: x-api-key
              format: '%s'
            - domain: claude.ai
              header: x-api-key
              format: '%s'
            - domain: mcp-proxy.anthropic.com
              header: x-api-key
              format: '%s'
      oauth:
        tokenEndpoint:
            host: platform.claude.com
            path: /v1/oauth/token
        sentinels:
            accessToken: sk-ant-oat01-proxy-managed
            refreshToken: sk-ant-ort01-proxy-managed
        credentialFile:
            path: ~/.claude/.credentials.json
            template: '{"claudeAiOauth":{"accessToken":"{{.AccessToken}}","refreshToken":"{{.RefreshToken}}","expiresAt":{{.ExpiresAt}},"scopes":{{.ScopesJSON}}}{{if .PrimaryApiKey}},"primaryApiKey":"{{.PrimaryApiKey}}"{{end}}}'
        skipIfEnv:
            - ANTHROPIC_API_KEY
environment:
    variables:
        IS_SANDBOX: "1"
setup:
    install:
        - command: |
            set -e
            ws="${WORKSPACE_DIR:-/}"
            esc=$(printf '%s' "$ws" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g; s/\r/\\r/g')
            projects="\"/\": { \"hasTrustDialogAccepted\": true }"
            [ "$ws" = "/" ] || projects="$projects, \"$esc\": { \"hasTrustDialogAccepted\": true }"
            printf '%s\n' "{
              \"bypassPermissionsModeAccepted\": true,
              \"hasCompletedOnboarding\": true,
              \"projects\": { $projects }
            }" > /home/agent/.claude.json
            chown agent:agent /home/agent/.claude.json
          user: "0"
          description: Seed Claude bypass/trust flags (root; overwrites image-shipped file)
        - command: |
            set -e
            mkdir -p /home/agent/.claude
            HELPER=''
            if [ "${SBX_CRED_ANTHROPIC_MODE:-none}" != none ]; then
              HELPER='  "apiKeyHelper": "echo proxy-managed",
            '
            fi
            printf '%s' "{
              \"themeId\": 1,
              \"alwaysThinkingEnabled\": true,
            ${HELPER}  \"permissions\": { \"defaultMode\": \"bypassPermissions\" },
              \"bypassPermissionsModeAccepted\": true,
              \"skipDangerousModePermissionPrompt\": true
            }
            " > /home/agent/.claude/settings.json
          user: agent
          description: Seed Claude settings.json from SBX_CRED_ANTHROPIC_MODE
        - command: |
            set -e
            [ -n "$MCP_GATEWAY_URL" ] || exit 0
            export PATH="$HOME/.local/bin:$HOME/.claude/local:$PATH"
            claude mcp add mcp-gateway "$MCP_GATEWAY_URL" \
              --transport http \
              --scope user \
              --header "Authorization: Bearer $MCP_SENTINEL_TOKEN_NAME" || true
          user: agent
          description: Register the sandbox MCP gateway with claude (install-time, race-free)
        - command: |
            set -e
            [ -n "$MCP_GATEWAY_URL" ] || exit 0
            node <<'NODE' || true
            const fs = require('fs');
            const path = require('path');
            const home = process.env.HOME || '/home/agent';
            const file = path.join(home, '.claude.json');
            try {
              const cfg = JSON.parse(fs.readFileSync(file, 'utf8'));
              const entry = cfg && typeof cfg === 'object' && cfg.mcpServers && typeof cfg.mcpServers === 'object'
                ? cfg.mcpServers['mcp-gateway']
                : undefined;
              if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
                entry.alwaysLoad = true;
                const tmp = file + '.mcp-gateway-alwaysload-tmp-' + process.pid;
                fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2) + '\n');
                fs.renameSync(tmp, file);
              }
            } catch (err) {
              console.error('claude-hsp-kit: could not merge alwaysLoad into ' + file + ': ' + err.message);
            }
            NODE
          user: agent
          description: Merge alwaysLoad:true into the mcp-gateway MCP server entry (install-time, always-load/direct-exposure)
    startup:
        - command:
            - sh
            - -c
            - chown -R agent:agent /home/agent/.claude/projects /home/agent/.claude/sessions /home/agent/.claude/todos /home/agent/.claude/shell-snapshots /home/agent/.claude/statsig 2>/dev/null || true
          user: "0"
          description: Re-own claude session-state volume mount roots to agent (block-volume mkfs trap)
        - command:
            - sh
            - -c
            - |
              set -e
              [ -n "$MCP_GATEWAY_URL" ] || exit 0
              export PATH="$HOME/.local/bin:$HOME/.claude/local:$PATH"
              claude mcp add mcp-gateway "$MCP_GATEWAY_URL" \
                --transport http \
                --scope user \
                --header "Authorization: Bearer $MCP_SENTINEL_TOKEN_NAME" || true
          user: agent
          description: Register the sandbox MCP gateway with claude (startup fallback)
        - command:
            - sh
            - -c
            - |
              set -e
              [ -n "$MCP_GATEWAY_URL" ] || exit 0
              node <<'NODE' || true
              const fs = require('fs');
              const path = require('path');
              const home = process.env.HOME || '/home/agent';
              const file = path.join(home, '.claude.json');
              try {
                const cfg = JSON.parse(fs.readFileSync(file, 'utf8'));
                const entry = cfg && typeof cfg === 'object' && cfg.mcpServers && typeof cfg.mcpServers === 'object'
                  ? cfg.mcpServers['mcp-gateway']
                  : undefined;
                if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
                  entry.alwaysLoad = true;
                  const tmp = file + '.mcp-gateway-alwaysload-tmp-' + process.pid;
                  fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2) + '\n');
                  fs.renameSync(tmp, file);
                }
              } catch (err) {
                console.error('claude-hsp-kit: could not merge alwaysLoad into ' + file + ': ' + err.message);
              }
              NODE
          user: agent
          description: Merge alwaysLoad:true into the mcp-gateway MCP server entry (startup fallback, always-load/direct-exposure)