Large codebases require automated AI assistants to surface patterns, locate code, and propose fixes. Existing tools scatter logic across many scripts, making it hard for a single engineer to understand or extend the workflow. HumanLayer bundles the AI‑agent orchestration, a daemon API, and a React UI into one repository, reducing the surface area for troubleshooting and accelerating iteration.
What This Does
HumanLayer presents a daemon (apps/daemon/src/index.ts) that runs a lightweight HTTP server (router/server.ts). The server exposes endpoints that the React UI (apps/react/src/App.tsx) calls to launch AI‑agent commands defined in the .claude/commands directory. The .claude folder contains Markdown command templates such as create_plan.md and implement_plan.md. Those templates are parsed at runtime to generate prompts for the AI backend, which is wrapped by the Go library in claudecode-go. A Docker image can be built from docker-compose.yml, allowing the daemon and UI to run as a single service stack.
How It Is Wired
Entry Point:apps/daemon/src/index.ts imports router/server.ts, which creates an Express app.
index.ts → router/index.ts → server.ts
server.ts registers routes like /api/plan and /api/implement.
Each route handler calls a helper in router/handlers.ts that reads the corresponding Markdown template from .claude/commands.
The handler forwards the rendered prompt to claudecode-go, a Go package that communicates with the AI backend via HTTP.
Responses are streamed back to the React front‑end.
The React UI (apps/react/src/App.tsx) renders a form that posts to /api/plan. The UI file imports utils/api.ts which uses fetch to hit the daemon.
External Touches:
Database: none.
Network: HTTP on localhost:3000 (daemon) and localhost:8080 (React).
Filesystem: reads Markdown command templates from .claude/commands.
Hub / Cycle: The daemon’s router is the single hub; all HTTP traffic flows through it, which simplifies security reviews but makes the daemon a potential single point of failure.
How To Use It
# Clone with the provided URL
git clone https://github.com/moses-y/humanlayer
# Build and start in Docker
docker compose up --build
The docker-compose.yml pulls the image built from the Makefile. If running locally without Docker:
# Install dependencies
cd apps/daemon
npm install
# Start the daemon
npm run dev # watches TS source
# In another terminal, start the React UI
cd apps/react
npm install
npm start
No additional environment variables are required; configuration lives in apps/daemon/.env if needed (currently empty).
Real‑World Use
A dev team can launch the daemon, then from the UI ask the AI to “find all TODO comments in the hld directory.” The UI sends /api/plan → the daemon renders find_todos.md → Go backend calls the AI model → result is streamed back and displayed in the React component.
Code Health & Issues
Tests: 106 test files, CI runs via .github/workflows/main.yml.
CI/CD: GitHub Actions present, linting via .pre-commit-config.yaml.
License: MIT (LICENSE).
Missing: No explicit type definitions for the Go‑TS boundary; the Go client uses any.
Complexity: The router (router/server.ts) has 42 internal calls, a moderate blast radius for change.
The Bottom Line
HumanLayer offers a cohesive stack for AI‑driven code assistance, with a clear separation between the daemon API, AI prompt templates, and a lightweight UI. Its Docker integration simplifies deployment, and the test coverage gives confidence for incremental changes. However, the lack of a database and the single‑hub router may limit scalability for larger teams. It is best suited for small to medium projects that need quick AI integration without a full‑blown microservice architecture.
What the analyser found
Deployment readiness
7/7
✓Container image
✓CI pipeline
✓Lockfile committed
✓Test suite
✓README
✓License
✓No committed secrets
Composition
794 files
TypeScript241
TSX171
Go106
Markdown81
JSON43
Shell16
ReactDocker
Module dependencies
The 10 most depended-upon modules of 338, from static import analysis. Red outline marks a module in an import cycle.
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
critical1
high2
medium7
low1
Keep secrets out of workflows a fork can triggercritical3 occurrences
.github/workflows/claude.yml
THOUGHTS_WRITE_DEPLOY_KEY, ANTHROPIC_API_KEY, LINEAR_API_KEY (publish or cloud scope)
Contributor-controlled input reaches a job holding a publish token or cloud credential, so a pull request becomes credential exfiltration.
Fix: Move secret-using steps into a workflow_run job that never checks out pull request code, or gate on an environment with required reviewers.
Pin third-party GitHub Actions to a commit SHAhigh7 occurrences
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Open a pull request instead of pushing to the default branchhigh
.github/workflows/linear-create-plan.yml
git push
Automated commits land on the branch that deploys, with no test having run against the result.
Fix: Push to a bot branch and open a pull request, or restrict the push to a tag ref.
Declare least-privilege permissions for GITHUB_TOKENmedium2 occurrences
.github/workflows/linear-create-plan.yml
2 workflow(s) declare no permissions, 1 of them reference secrets
With no declaration the token inherits the repository default, so any injected step can push commits or mint releases from inside your own CI.
Fix: Add permissions: contents: read at the top of the workflow and widen per job only where needed.
Enable Dependabot or Renovatemedium
14 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Install from the lockfile in CImedium
.github/workflows
npm install with a committed package-lock.json
A fresh resolution in CI means the tested dependency set is not the locked one, so the failure only appears after merge.
Fix: Use npm ci, yarn install --immutable, or pnpm install --frozen-lockfile.
Pin the container base image by digestmedium
docs/Dockerfile
node:21-slim
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Gate pull requests on a dependency vulnerability scanmedium
.github/workflows
no dependency scan in CI
This is the one gate that would catch a known-vulnerable package before it reaches a build, and no repository in the sample had it.
Fix: Add dependency-review-action on pull_request, or osv-scanner on push and a schedule.
Set persist-credentials: false on checkoutmedium
.github/workflows/claude.yml
checkout keeps the token, then dependencies are installed
The token stays in .git/config for every later step, so a malicious postinstall script reads a pushable credential without one ever being passed to it.
Fix: Add with: persist-credentials: false, and pass an explicit token only to the step that pushes.
Add a non-root USER to the imagemedium
docs/Dockerfile
CMD or ENTRYPOINT with no USER directive
A process running as root in the container is root against every mounted volume, and it turns any container escape or writable-mount mistake from a contained problem into a host one.
Fix: Create an unprivileged user, chown what it needs, and end the Dockerfile with USER.
Set timeout-minutes on the workflow jobslow4 occurrences
.github/workflows/claude.yml
4 workflow(s) declare no job timeout
A wedged step runs to the six-hour platform default, which on a two-hourly schedule means three runs overlap behind it.
Fix: Add timeout-minutes with a realistic bound to each job.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.