Hootline is an Eve agent that repairs failing GitHub Actions and GitLab CI pipelines. It accepts signed provider webhooks, loads repository policy, gathers redacted failure context, starts a bounded Eve repair session, and publishes only policy-allowed fixes that pass the configured checks.
At runtime, trusted Hootline code handles provider credentials, webhook
verification, repository archive download, failure-log collection, delivery
dedupe, attempt limits, publishing, reruns, and auto-merge follow-up. The model
works inside Eve's sandbox after calling stage_repository_snapshot, edits
/workspace/repo, runs run_repo_checks, and finishes with publish_fix,
rerun_pipeline, or post_provider_comment.
- Providers:
- GitHub
workflow_runandcheck_suitecompletion webhooks. - GitLab pipeline webhooks.
- GitHub
- Publish modes:
pr_mr: create or update a fixer branch and PR/MR. This is the default.push_branch: create or update the fixer branch only.auto_merge: create or update a PR/MR and record it for deterministic merge after a successful follow-up pipeline webhook.
- Guardrails:
- Repository opt-in through a default-branch
.hootline.yaml. - Branch allowlist before a repair starts.
- File allowlist before publishing.
- Maximum attempts per provider/repo/SHA/pipeline key.
- Repository archive and changed-file payload size caps.
- Verification commands run in
/workspace/repobefore publishing. - Sandbox network defaults to deny-all unless policy lists allowed hosts.
- Secrets and secret-looking text are redacted from logs and tool-visible command output.
- Repository opt-in through a default-branch
See docs/architecture.md for the module boundaries and
benchmark harness structure. In short: agent/ is the Eve-authored runtime
surface, provider side effects stay in trusted runtime code, agent/tools/ stay
thin and model-facing, and reusable benchmark logic lives under
scripts/benchmarks/.
Install dependencies:
npm installCreate a local service environment file:
cp .env.example .env.localCommit a Hootline policy file to every repository Hootline should repair:
cp config/hootline.example.yaml /path/to/repo/.hootline.yamlStart the local Eve daemon:
npm run dev -- --host 127.0.0.1 --port 3000Expose that daemon with your tunnel of choice and configure provider webhooks:
- GitHub:
POST /eve/v1/ci/github - GitLab:
POST /eve/v1/ci/gitlab
Build the production image locally:
docker build -t ghcr.io/openhoo/hootline:local .Eve compiles the selected model into the image at build time. Use build args when the image should route to a non-default provider:
docker build \
--build-arg HOOTLINE_MODEL_PROVIDER=openai-compatible \
--build-arg HOOTLINE_MODEL=gemma-4-31b \
--build-arg HOOTLINE_MODEL_BASE_URL=https://api.cerebras.ai/v1 \
--build-arg HOOTLINE_MODEL_CONTEXT_WINDOW_TOKENS=32000 \
--build-arg HOOTLINE_MODEL_PROVIDER_NAME=cerebras \
-t ghcr.io/openhoo/hootline:local .Run the image with runtime credentials and persistent service state:
docker run --rm \
-p 127.0.0.1:3000:3000 \
-v hootline-data:/data \
-e HOOTLINE_MODEL_API_KEY \
-e GITHUB_APP_ID \
-e GITHUB_APP_PRIVATE_KEY \
-e GITHUB_WEBHOOK_SECRET \
ghcr.io/openhoo/hootline:localWhen repairs need Eve's local Docker sandbox backend, make the host Docker daemon available to the container:
docker run --rm \
-p 127.0.0.1:3000:3000 \
-v hootline-data:/data \
-v /var/run/docker.sock:/var/run/docker.sock \
--group-add "$(stat -c '%g' /var/run/docker.sock)" \
--env-file .env.local \
ghcr.io/openhoo/hootline:localThe image listens on 0.0.0.0:${PORT:-3000}, persists Hootline state at
/data/hootline-state.json by default, and exposes /eve/v1/health for Docker
health checks. Do not pass real model or provider API keys as Docker build args;
provide them only as runtime environment variables.
CI runs typecheck, tests, model matrix validation, an Eve production build, a
Docker build, and Conventional Commit linting through Hooversion. After CI
passes on main, the release workflow runs Hooversion to derive the next
semantic version from Conventional Commits, update package.json,
package-lock.json, and CHANGELOG.md, create a GitHub release, then publish a
multi-arch image to GitHub Container Registry.
The package.json is intentionally private; Hootline is released as source,
GitHub releases, and GHCR images, not as a public npm package.
Published image tags:
ghcr.io/openhoo/hootline:<version>ghcr.io/openhoo/hootline:<major>.<minor>ghcr.io/openhoo/hootline:sha-<commit>ghcr.io/openhoo/hootline:latest
Core settings:
HOST: bind address used by Eve. Defaults to the runtime's framework default; the container sets0.0.0.0.PORT: HTTP port used by Eve. The container defaults to3000.HOOTLINE_STATE_PATH: durable JSON state file for delivery dedupe, attempts, verification results, publish records, rerun records, and pending auto-merge records. Defaults tovar/hootline-state.json.HOOTLINE_REPO_CONFIG_PATH: repo-local policy path. Defaults to.hootline.yaml.HOOTLINE_LOG_LEVEL: Hootline log level,trace|debug|info|warn|error|fatal|silent. Defaults toinfo.EVE_LOG_LEVEL: Eve framework log level.HOOTLINE_TELEMETRY_MODE:local+otlp,local,otlp, oroff. Defaults tolocal+otlp. OTLP export starts only when a standard OTLP endpoint env var is configured.HOOTLINE_TELEMETRY_DETAIL:metadata,summary, orfull. Defaults tofull. Full detail stores redacted tool inputs/outputs and assistant text in local JSONL; reasoning text is never stored.HOOTLINE_TELEMETRY_PATH: local append-only telemetry JSONL path. Defaults tovar/hootline-telemetry.jsonl.HOOTLINE_TELEMETRY_MAX_TEXT_CHARS: per-string cap before telemetry text is truncated. Defaults to12000.OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, orOTEL_EXPORTER_OTLP_METRICS_ENDPOINT: enable optional OTLP HTTP export when telemetry mode includesotlp.
Model settings:
HOOTLINE_MODEL_PROVIDER: one ofanthropic,openai,openai-compatible, orgateway. Defaults toanthropic.HOOTLINE_MODEL: model id. Defaults depend on provider:claude-sonnet-4-6,gpt-5.1,gpt-oss-120b, oranthropic/claude-sonnet-4.6.HOOTLINE_MODEL_CONTEXT_WINDOW_TOKENS: optional for direct providers, required foropenai-compatible. Must be between4096and32000.HOOTLINE_MODEL_API_KEY: provider-specific override credential.HOOTLINE_MODEL_BASE_URL: optional for direct Anthropic/OpenAI, required foropenai-compatible.HOOTLINE_MODEL_PROVIDER_NAME: metadata label foropenai-compatible.ANTHROPIC_API_KEYorANTHROPIC_AUTH_TOKEN: direct Anthropic credential.OPENAI_API_KEY: direct OpenAI credential.AI_GATEWAY_API_KEYorVERCEL_OIDC_TOKEN: explicit gateway mode credential.
GitHub settings:
GITHUB_APP_IDGITHUB_APP_PRIVATE_KEYGITHUB_WEBHOOK_SECRET
GitLab settings:
GITLAB_BASE_URL: defaults tohttps://gitlab.com.GITLAB_TOKEN: API token used to read archives/logs, push branches, create or update MRs, retry pipelines, merge MRs, and post comments.GITLAB_SIGNING_TOKEN: GitLab Standard Webhook signing token. Preferred.GITLAB_SECRET_TOKEN: optional legacyX-Gitlab-Tokenfallback. This is only accepted for repositories whose policy setsallowGitlabSecretTokenFallback: true.
Direct Anthropic:
HOOTLINE_MODEL_PROVIDER=anthropic
HOOTLINE_MODEL=claude-sonnet-4-6
ANTHROPIC_API_KEY=...Direct OpenAI:
HOOTLINE_MODEL_PROVIDER=openai
HOOTLINE_MODEL=gpt-5.1
OPENAI_API_KEY=...Local or hosted OpenAI-compatible endpoint:
HOOTLINE_MODEL_PROVIDER=openai-compatible
HOOTLINE_MODEL=local-coder
HOOTLINE_MODEL_BASE_URL=http://127.0.0.1:11434/v1
HOOTLINE_MODEL_CONTEXT_WINDOW_TOKENS=32000
HOOTLINE_MODEL_API_KEY=...Explicit AI Gateway:
HOOTLINE_MODEL_PROVIDER=gateway
HOOTLINE_MODEL=anthropic/claude-sonnet-4.6
AI_GATEWAY_API_KEY=...Hootline reads version: 1 YAML from the target repository's default-branch
.hootline.yaml. config/hootline.example.yaml is the canonical starting
point. Missing .hootline.yaml means the repository is not configured for
repairs.
Repository policy fields:
mode:pr_mr,push_branch, orauto_merge.allowedBranches: required event refs that may start repairs.allowedFileGlobs: required changed paths thatpublish_fixmay publish.verificationCommands: required commands run inside/workspace/repobyrun_repo_checksand again bypublish_fix. At least one command is required sopublish_fixcannot publish without a repository-defined check.sandboxNetworkAllow: host allowlist for verification command network access. Empty means deny-all.fixBranchPrefix: prefix for generated fixer branches.maxAttemptsPerSha: repair attempts allowed per provider/repo/SHA/pipeline.maxSnapshotBytes: archive and changed payload size limit.allowGitlabSecretTokenFallback: opt-in for legacy GitLab secret-token webhook verification.autoMerge.requireSuccessfulPipeline: when true, auto-merge waits for a later successful pipeline webhook on the fixer branch.autoMerge.deleteSourceBranch: delete the fixer branch after merge when the provider supports it.
Policy globs are intentionally small: * and ? match within a path segment,
while ** may cross directories.
Run a single Hootline process per state file. State writes are serialized inside one Node process; separate processes or replicas sharing the same state path can race delivery dedupe, repair-slot claims, and auto-merge claims. Use one container/replica per state file unless the state backend is replaced with a durable cross-process lock.
Model/provider API failures are retried inside the same repair slot before Hootline falls back to provider redelivery. Configure this with:
HOOTLINE_PROVIDER_ERROR_RETRIES: retryable provider-error sessions to start after the initial Eve session fails. Default2.HOOTLINE_PROVIDER_ERROR_RETRY_BASE_MS: exponential backoff base delay. Default1000.HOOTLINE_PROVIDER_ERROR_RETRY_MAX_MS: backoff cap. Default15000.
These retries do not increment repository maxAttemptsPerSha; that policy still
limits provider webhook repair attempts.
Hootline uses a GitHub App for webhook verification, archive reads, job log
reads, failed-job reruns, branch writes, PR creation or updates, comments, and
merges. The manifest helper requires an authenticated gh CLI.
For local testing, start Hootline, expose it through a public HTTPS tunnel, then run:
npm run github-app:setup -- --webhook-base-url https://your-tunnel.exampleUse --webhook-url instead when you already have the full GitHub webhook URL:
npm run github-app:setup -- --webhook-url https://example.com/eve/v1/ci/githubFor organization-owned apps:
npm run github-app:setup -- --owner-type org --owner your-org --webhook-base-url https://your-tunnel.exampleOpen the printed local URL and submit the manifest form. After GitHub redirects
back, the helper exchanges the manifest code and writes credentials under
var/github-app/ with mode 0600.
Source the generated environment before starting Hootline:
set -a
source .env.local
source var/github-app/<app-slug>.env
set +a
npm run dev -- --host 127.0.0.1 --port 3000Verify the app can see a repository:
source var/github-app/<app-slug>.env
npm run github-app:check-installation -- owner/repoThe generated app requests repository permissions for Actions write, Checks
read, Contents write, Issues write, Metadata read, and Pull requests write. It
subscribes to workflow_run and check_suite events.
benchmark:sim is the primary benchmark path for agent and harness changes. It
runs the full Eve repair loop against a simulated GitHub provider, so it does
not reset a real repository, call gh, wait on GitHub Actions, or require a
public webhook tunnel. The runner materializes a local benchmark repo from one
of the realistic project fixtures under benchmarks/fixtures/projects/,
injects the selected scenario, sends a signed synthetic workflow_run webhook
to Hootline, and records the simulated PR/check result under
var/simulated-benchmarks/.
Preview the selected scenarios without starting Hootline:
npm run benchmark:sim:dry-runDry runs are read-only: they validate fixture metadata and print rows without creating benchmark artifacts or simulator state.
Run one deterministic smoke sample with Eve's mock model:
npm run benchmark:sim -- --mock-model --scenarios commerce-domain-shipping-threshold --samples 1Run only one fixture project:
npm run benchmark:sim:dry-run -- --projects support-deskRun the same simulated harness with the configured real model:
npm run benchmark:sim -- --scenarios all --samples 1Run multiple simulated scenario samples concurrently:
npm run benchmark:sim -- --mock-model --scenarios all --samples 1 --concurrency 4Use --artifact-dir <path> to write a non-dry-run's artifacts to a specific
directory. Fixture verification commands time out after 60000 ms by default;
override with --fixture-command-timeout-ms <ms> when a scenario needs a
different bound.
The benchmark projects are intentionally larger than the individual scenario
mutations. The default suite currently includes commerce-platform and
support-desk, each as a fullstack Bun workspace monorepo with React/Vite
TypeScript clients, Express TypeScript APIs, shared contracts, domain packages,
test utilities, bun.lock, and repo-local .hootline.yaml policy. The current
scenario generation replaces the older simple fixture catalog with 20 fullstack
scenarios covering client behavior, API validation, shared contracts, domain
logic, and multi-file cascade repairs.
Fixture verification installs dependencies from the fixture lockfile and then
runs the fixture's typecheck, build, and test suite with Bun. Because Bun
resolves public packages from the npm registry, fixture policies allow
registry.npmjs.org during verification and keep node_modules/ plus build
output ignored so dependency installs do not become publishable changes.
The runner starts a built local Eve server automatically unless --server-url
is supplied. For auto-started servers it sets
HOOTLINE_GITHUB_PROVIDER_BACKEND=simulated,
HOOTLINE_SIMULATOR_STATE_PATH, HOOTLINE_STATE_PATH, and a synthetic
GITHUB_WEBHOOK_SECRET for the child process. When using --server-url, start
the server yourself with matching env values.
Rows include repair metadata and simulated PR/check details, including actual
changed files, expected-vs-actual repair file match, and simulated branch check
conclusion. Shared row/status/summary helpers live in
scripts/benchmarks/common.mjs.
The benchmark waits for Hootline to publish or terminate the repair attempt, waits for simulated fixer PR checks when a PR is opened, and records attempt count, tool sequence, failed tools, continuation count, token usage, terminal action, PR URL, PR check result, scenario complexity, scenario tags, mutation count, project id, verification commands, the expected repair file set, summary breakdowns by project, complexity, tag, and mutation count, and non-green improvement signals.
Scenario ids are listed by npm run benchmark:sim -- --help. The catalog favors
a bounded set of realistic regressions over broad scenario volume: most rows are
single-rule issues inside larger applications, while complex rows require
multi-file repairs across domain boundaries.
For local GitHub webhook testing without a permanent public host:
cloudflared tunnel --url http://127.0.0.1:3000
npm run github-app:setup -- --webhook-base-url https://your-tunnel.trycloudflare.comQuick tunnel URLs are temporary. If the tunnel restarts, update the GitHub App webhook URL or rerun the manifest helper with the new URL.
Configure a GitLab pipeline webhook to call:
https://your-hootline-host/eve/v1/ci/gitlab
Prefer GitLab Standard Webhooks and set GITLAB_SIGNING_TOKEN to the signing
secret. For older integrations that only send X-Gitlab-Token, set
GITLAB_SECRET_TOKEN and opt in per repository:
version: 1
allowGitlabSecretTokenFallback: trueThe fallback is weaker than Standard Webhook signatures and is rejected unless policy explicitly enables it.
Each repair session starts with four seeded context blocks:
Hootline state: the activeattemptKey.Normalized pipeline event: provider, repo, ref, SHA, pipeline/run ids, actor, and PR/MR metadata when available.Repository policy: publish mode, allowed branches, allowed file globs, verification commands, sandbox network allowlist, attempt limits, and auto-merge settings.Initial failure context collected by trusted runtime code: redacted failed job metadata and log snippets, or a collection error.
The model should call get_failure_context only when the seeded logs are
missing, stale, truncated across the root-cause area, or contradicted by later
tool output. Repository files are not preloaded; the model must call
stage_repository_snapshot before reading, editing, checking, or publishing.
Normal repair flow:
- Inspect the seeded event, policy, and failure context.
- Stage
/workspace/repo. - Find the earliest causal CI error.
- Make the smallest allowed fix.
- Run
run_repo_checks. - Call
publish_fixafter relevant checks pass. - Request a transient rerun or post a provider comment when no safe code fix is appropriate.
publish_fix reruns the configured verification commands and rejects changed
paths outside allowedFileGlobs before calling provider APIs.
Hootline emits structured logs through agent/lib/logger.ts. Every dynamic
value is passed through agent/lib/redact.ts before it is written.
- Pretty, colorized output is used when stdout is a TTY.
- JSON lines are used in production/CI.
- Every line includes
ns, such aschannels.ci,tools.publish_fix,providers.github,providers.gitlab,lib.config, orlib.sandbox. - Repair lifecycle logs carry
attemptKey,provider,repoSlug, anddeliveryKeyonce available. - Logged errors include stable redacted
errorIdvalues for support correlation.
Tests run with HOOTLINE_LOG_LEVEL=silent and HOOTLINE_TELEMETRY_MODE=off so
application logs and local telemetry artifacts do not interleave with test
output.
Hootline records agent telemetry through two Eve-supported surfaces:
agent/instrumentation.tsattaches Hootline repair context to AI SDK telemetry spans and starts the OpenTelemetry Node SDK when OTLP export is enabled and an OTLP endpoint is configured.agent/hooks/telemetry.tsobserves Eve's durable stream events after they are recorded and writes correlated local JSONL records.
Local telemetry captures repair lifecycle decisions, session boundaries, model
step token usage, cache token usage, tool call order, failed/rejected tools,
continuations, provider-error retries, and terminal actions. Records are
redacted with the same secret patterns used by logs. metadata mode keeps
routing, status, timing, token, and tool-name fields while omitting IO. summary
mode stores capped redacted summaries. full mode stores full redacted payloads
subject to HOOTLINE_TELEMETRY_MAX_TEXT_CHARS.
OTLP spans and metrics are metadata-oriented by default. Full redacted IO is kept in the local JSONL artifact, not sent as model input/output span content.
Eve stores local repair session streams under .workflow-data. When a repair
appears quiet, inspect the visible session transcript and tool events:
npm run session:inspect -- wrun_01KWBMAS9SGYY60502V4G11CYEThe report includes event counts, tool-call order, step finish reasons, token
usage, tool failures, correlated telemetry counts, the final assistant-message
excerpt, and whether Hootline state recorded a staged repo, verification,
publish, or rerun. Use --message-chars to change excerpt length,
--telemetry to point at a non-default JSONL artifact, or --json for
machine-readable output.
You can also read the live Eve stream while the dev server is running:
GET /eve/v1/session/<session-id>/stream
Eve owns Hootline's durable model loop: sessions, step replay, compaction,
streaming events, tool execution, sandboxed shell/file access, and the
app-runtime/sandbox boundary. Hootline intentionally does not wrap the agent in
AI SDK ToolLoopAgent, streamText loops, or ai-sdk-tools state/artifact
stores.
The current ai dependency remains because Eve declares it as a peer dependency
and Hootline uses AI SDK provider packages to resolve the configured language
model.
The default web_fetch and web_search tools are disabled. Provider APIs are
available only through narrow Hootline tools bound to the active attemptKey
and repository policy.
npm run dev -- --host 127.0.0.1 --port 3000
npm run info
npm run build
npm run lint
npm run typecheck
npm test
npm run check:model-matrix
npm exec --yes @openhoo/hooversion@0.1.1 -- plan
docker build -t ghcr.io/openhoo/hootline:local .
npm run benchmark:sim:dry-run
npm run session:inspect -- <session-id>
npm run github-app:setup -- --helpFull local verification:
npm run lint
npm test
npm run check:model-matrix
ANTHROPIC_API_KEY=test HOOTLINE_MODEL_PROVIDER=anthropic HOOTLINE_MODEL=claude-sonnet-4-6 npm run info
ANTHROPIC_API_KEY=test HOOTLINE_MODEL_PROVIDER=anthropic HOOTLINE_MODEL=claude-sonnet-4-6 npm run build