hooks

package
v0.0.0-...-7290607 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 14, 2026 License: AGPL-3.0 Imports: 50 Imported by: 0

README

Hooks Service

This service handles Claude Code hook events for tool usage observability, providing real-time tracking of tool calls with user attribution.

Architecture Overview

The hooks service uses a Redis-buffered validation pattern to handle the timing mismatch between unauthenticated hook events and authenticated session metadata.

The Challenge

Claude Code sends two types of requests to our service:

  1. Hook Events (unauthenticated) - PreToolUse, PostToolUse, PostToolUseFailure

    • (Usually) arrive first (immediately when tools are called)
    • Contain tool information (including session_id) but no user identity
    • Cannot validate authenticity
  2. Logs Events (authenticated via API key)

    • (Usually) arrive later (after session established)
    • Contain session_id, user_email, organization.id
    • Validated through API key authentication

Problem: Hook events often arrive before we know the user's identity via the Logs endpoint.

The Solution: Redis Buffering
┌─────────────────────────────────────────────────────────────────┐
│                      Hook Event Flow                            │
└─────────────────────────────────────────────────────────────────┘

Hook Event Arrives
       │
       ├──> Check Redis: "session:metadata:{session_id}"
       │
       ├──> FOUND: Session validated! ✓
       │    └──> Write directly to ClickHouse with:
       │         • user_email
       │         • gram_org_id
       │         • project_id
       │         • tool call data
       │
       └──> NOT FOUND: Session not yet validated
            └──> Buffer in Redis: "hook:pending:{session_id}:{tool_use_id}"
                 • TTL: 10 minutes
                 • Contains full hook payload
                 • Will be processed when session validates


┌─────────────────────────────────────────────────────────────────┐
│                      Logs Event Flow                            │
└─────────────────────────────────────────────────────────────────┘

Logs Event Arrives (Authenticated)
       │
       ├──> Validate API Key ✓
       │
       ├──> Extract:
       │    • session_id
       │    • user_email
       │    • Claude organization.id
       │
       ├──> Get gram_org_id & project_id from auth context
       │
       └──> Store in Redis: "session:metadata:{session_id}"
            • TTL: 24 hours
            • Future hooks will find this and write directly to ClickHouse
            • Buffered hooks will be processed on next call (within 10min window)

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (

	// ErrChatNotFound indicates the chat (conversation) does not exist.
	ErrChatNotFound = errors.New("chat not found")
)

Functions

func Attach

func Attach(mux goahttp.Muxer, service *Service)

func AttachServerNames

func AttachServerNames(mux goahttp.Muxer, service *Service)

func NewLocalSessionCache

func NewLocalSessionCache(underlying cache.Cache, db *pgxpool.Pool) cache.Cache

NewLocalSessionCache creates a cache wrapper that handles session metadata lookups locally without requiring OTEL telemetry export.

Types

type ChatTitleGenerator

type ChatTitleGenerator interface {
	ScheduleChatTitleGeneration(ctx context.Context, chatID, orgID, projectID string) error
}

ChatTitleGenerator schedules async chat title generation.

type HookSpecificOutput

type HookSpecificOutput struct {
	HookEventName            *string `json:"hookEventName,omitempty"`
	AdditionalContext        *string `json:"additionalContext,omitempty"`
	PermissionDecision       *string `json:"permissionDecision,omitempty"`
	PermissionDecisionReason *string `json:"permissionDecisionReason,omitempty"`
}

HookSpecificOutput is the structure for hook-specific output in responses

type MetricDataPoint

type MetricDataPoint struct {
	SessionID           string
	Model               string
	UserEmail           string
	InputTokens         int64
	OutputTokens        int64
	CacheReadTokens     int64
	CacheCreationTokens int64
	Cost                float64
	TimestampNano       int64
}

MetricDataPoint represents a single metric aggregated across all data points for a model+session

type NameMappingCache

type NameMappingCache interface {
	// Get retrieves a name mapping from cache. Returns empty string if not found.
	Get(ctx context.Context, serverName string) (string, error)

	// Save stores a name mapping in cache
	Save(ctx context.Context, serverName, mappedName string) error
}

NameMappingCache defines the interface for storing and retrieving name mappings

type OTELLogData

type OTELLogData struct {
	SessionID   string
	UserEmail   string
	ClaudeOrgID string
}

OTELLogData contains extracted data from an OTEL log record

type ProductFeaturesClient

type ProductFeaturesClient interface {
	IsFeatureEnabled(ctx context.Context, organizationID string, feature productfeatures.Feature) (bool, error)
}

ProductFeaturesClient checks whether product features are enabled for an org.

type Service

type Service struct {
	// contains filtered or unexported fields
}

func NewService

func NewService(
	logger *slog.Logger,
	db *pgxpool.Pool,
	tracerProvider trace.TracerProvider,
	telemetryLogger *telemetry.Logger,
	sessionsMgr *sessions.Manager,
	cacheAdapter cache.Cache,
	completionsClient openrouter.CompletionClient,
	temporalEnv *tenv.Environment,
	authz *authz.Engine,
	pfClient ProductFeaturesClient,
	chatTitleGenerator ChatTitleGenerator,
	riskScanner risk.RiskScanner,
	shadowMCPClient *shadowmcp.Client,
	writer *chat.ChatMessageWriter,
) *Service

func (*Service) APIKeyAuth

func (s *Service) APIKeyAuth(ctx context.Context, key string, schema *security.APIKeyScheme) (context.Context, error)

func (*Service) Claude

func (s *Service) Claude(ctx context.Context, payload *gen.ClaudePayload) (*gen.ClaudeHookResult, error)

Claude is the unified endpoint for all Claude Code hook events.

func (*Service) Codex

func (s *Service) Codex(ctx context.Context, payload *gen.CodexPayload) (*gen.CodexHookResult, error)

func (*Service) Cursor

func (s *Service) Cursor(ctx context.Context, payload *gen.CursorPayload) (*gen.CursorHookResult, error)

Cursor is the endpoint for Cursor hook events

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, payload *gen.DeletePayload) error

Delete deletes a server name display override

func (*Service) List

func (s *Service) List(ctx context.Context, payload *gen.ListPayload) ([]*gen.ServerNameOverride, error)

List lists all server name display overrides for the authenticated project

func (*Service) Logs

func (s *Service) Logs(ctx context.Context, payload *gen.LogsPayload) error

Logs handles authenticated OTEL logs data from Claude Code

func (*Service) Metrics

func (s *Service) Metrics(ctx context.Context, payload *gen.MetricsPayload) error

Metrics handles authenticated OTEL metrics data from Claude Code

func (*Service) Upsert

func (s *Service) Upsert(ctx context.Context, payload *gen.UpsertPayload) (*gen.ServerNameOverride, error)

Upsert creates or updates a server name display override

type SessionMetadata

type SessionMetadata struct {
	SessionID   string
	ServiceName string
	UserEmail   string
	UserID      string
	ClaudeOrgID string
	GramOrgID   string
	ProjectID   string
}

SessionMetadata contains validated session information from the Logs endpoint

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL