agentleFS
joewalnesjoewalnes/websocketd/CLAUDE.md

websocketd

CLAUDE.md17k starsChanged 55 days agoBSD-2-ClauseView on GitHub

A small command-line tool (Go) that wraps an existing CLI program and exposes it via WebSocket. Any program that reads STDIN and writes STDOUT becomes a WebSocket server. Unit tests are in libwebsocketd/. Integration tests are in qa/integration/. .github/workflows/test.yml's lint job runs go vet, gofmt -l, staticcheck, and gosec on every push and pull request; there is no separate local lint command to run before that. The qa/integration tests build the websocketd binary from source in TestMain, but go test…

  • Commits and pushes

We split this CLAUDE.md into the rules it gives an agent

18 rules. About 0 are common, 13 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈13

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Distinctive:

    When you make a mistake (especially forgetting something the user asked for):

    line 29
  2. Distinctive:

    Don't just apologize — fix the system.

    line 33
  3. Distinctive:

    When the user expresses a coding preference, convention, or correction during a session, offer to encode it into this CLAUDE.md file so it …

    line 37
  4. Distinctive:

    Update README.md (and any relevant docs) before committing if the change affects:

    line 41
  5. Very specific:

    Update CHANGES before committing if the change affects:

    line 71
  6. Distinctive:

    Every Go source file starts with the 4-line BSD copyright header.

    line 87
  7. Distinctive:

    Before implementing a feature or fix:

    line 96
  8. Distinctive:

    Write a test that captures the expected behavior

    line 97
  9. Distinctive:

    Run it — verify it fails (if it passes, the test isn't testing the right thing)

    line 98
  10. Distinctive:

    When a test captures a process's output, capture stdout and stderr separately and assert on the specific stream — never merge them.

    line 103
  11. Very specific:

    Run tests: go test ./... — do not commit if tests fail

    line 110
  12. Very specific:

    If the change affects user-visible behavior, dependencies, or bug fixes: update CHANGES in the same commit (not a follow-up)

    line 111
  13. Distinctive:

    Never merge on an agent's report.

    line 146
  14. Distinctive:

    Run the suite from inside its own module instead:

    line 181
  15. Distinctive:

    If an agent delegates a question to a subagent, its findings may be cited only after that subagent's completion notification has arrived.

    line 194
  16. Distinctive:

    TCP ports: Always bind port 0 / allocate a free port.

    line 229
  17. Very specific:

    SECURITY_AUDIT.md stays deleted: Do not reinstate it.

    line 260
  18. Very specific:

    website/CNAME: must not be deleted or renamed.

    line 261

≈ “Common” is an estimate: at least 1 in 5 of the 25,680 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

websocketd

A small command-line tool (Go) that wraps an existing CLI program and exposes it via WebSocket. Any program that reads STDIN and writes STDOUT becomes a WebSocket server.

Build

go build

Test

go test ./...

Unit tests are in libwebsocketd/. Integration tests are in qa/integration/. .github/workflows/test.yml's lint job runs go vet, gofmt -l, staticcheck, and gosec on every push and pull request; there is no separate local lint command to run before that.

The qa/integration tests build the websocketd binary from source in TestMain, but go test caches the result on that package's own inputs. Changing libwebsocketd/, main.go, or config.go does not invalidate the integration cache even though the built binary's behavior changes — a cached green can hide a real break. After changing any non-qa/integration package, re-run with go test -count=1 ./... (and -race for concurrency changes).

Mistake retrospectives

When you make a mistake (especially forgetting something the user asked for):

  1. Acknowledge it directly
  2. Identify the root cause — why did this happen?
  3. Suggest a concrete project change to prevent recurrence (add a rule to CLAUDE.md, add a pre-commit check, etc.) Don't just apologize — fix the system.

Evolving preferences

When the user expresses a coding preference, convention, or correction during a session, offer to encode it into this CLAUDE.md file so it persists across sessions.

Documentation

Update README.md (and any relevant docs) before committing if the change affects:

  • Public API, CLI interface, or configuration
  • Setup/installation steps
  • Feature behavior visible to users

The public website (websocketd.com) lives in website/ on this branch and deploys via .github/workflows/pages.yml. It is not a separate branch, so check it alongside README.md for the same changes — the tutorial and feature list there go stale in exactly the same way. website/CNAME carries the custom domain and must not be deleted or renamed.

Which surface does it go on

The docs site (docsite/, served at websocketd.com/docs) is the only surface allowed to be long; the others are short because it exists.

  • --help is the single source of truth for flag text. tools/gendocs generates the man page and reference/cli-flags from it. Never hand-write a third copy.
  • README.md serves two readers briefly, one evaluating and one about to build, and points onward for everything else.
  • website/index.html is marketing and orientation: no flag reference, no deploy recipes, no tutorial. A visitor scrolling past the fold for "how do I actually use this" means content landed on the wrong surface.

pages.yml copies website/ verbatim to the site root, so anything left in that directory is published. Planning documents go in docs-archive/.

Changelog

Update CHANGES before committing if the change affects:

  • Dependencies (especially security fixes)
  • User-visible behavior, CLI flags, or defaults
  • New features or test infrastructure
  • Bug fixes referenced by issue number

Latest version goes at the top. Follow the existing format.

Classification: "Breaking change" is reserved for changes that require operators to act before upgrading (config migrations, removed/renamed flags, tightened accept criteria). Bug fixes that observably change behavior stay classified as fixes — fixing a bug isn't breaking a promise. Leading a version's entry with a "Breaking changes" block is fine; keep it honest.

License headers

Every Go source file starts with the 4-line BSD copyright header. New files use the current year ("Copyright 2026 ..."), not the year copied from an existing file. Existing files keep their original year. When adding a missing header to an old file, use the year the file first appeared (git log --follow --format=%ad --date=short -- <file> | tail -1) — don't assume 2013.

Test-first

Before implementing a feature or fix:

  1. Write a test that captures the expected behavior
  2. Run it — verify it fails (if it passes, the test isn't testing the right thing)
  3. Implement until the test passes

Test precision

When a test captures a process's output, capture stdout and stderr separately and assert on the specific stream — never merge them. Which stream something is logged to is part of the behavior under test.

Pre-commit checks

Before every commit:

  1. Run tests: go test ./... — do not commit if tests fail
  2. If the change affects user-visible behavior, dependencies, or bug fixes: update CHANGES in the same commit (not a follow-up)

Commits

Break work into small atomic commits — one logical change per commit. Don't bundle unrelated changes. A bug fix, a new feature, and a refactor are three commits, not one.

Engineering diary

Maintain DIARY.md — add an entry when making significant changes, architectural decisions, or non-obvious tradeoffs. Latest entries at top. Focus on why and context, not what (that's in the commits).

Bug tracking

Bugs and tasks are tracked in GitHub Issues. Use gh issue list to view and gh issue create to add new ones.

Agent operations

Configuration for /go-team and any other unattended multi-agent run.

Setup version: project-setup 2026-09-02

Requests lane: ASKS.md. Joe's own requests live there and outrank anything an agent invents for itself. One agent must always be working its top open item.

Lesson ledger: LESSONS.md. Append only when something actually bit; prefer sharpening an existing entry over adding a near-duplicate.

Autonomy: merge locally only. Agents may merge verified work to local main; nothing is pushed to origin. git push is deliberately absent from .claude/settings.json. Joe reviews before anything leaves the machine.

Crew size: 3.

Verification recipe

Never merge on an agent's report. Run this yourself, in a throwaway worktree on the agent's branch — never in the shared checkout:

go build ./...
go test -count=1 ./...          # -race as well for concurrency changes

go test alone is not sufficient evidence — see the integration-cache note under Test above; -count=1 is what makes a green meaningful after a change to libwebsocketd/, main.go, or config.go.

For anything a user would notice, also drive the real binary:

# pick a free port; never a fixed one (see singletons)
PORT=$(python3 -c 'import socket;s=socket.socket();s.bind(("127.0.0.1",0));print(s.getsockname()[1]);s.close()')
./websocketd --port=$PORT --devconsole ./qa/integration/testcmd echo
curl -sS -D- "http://127.0.0.1:$PORT/" | head -40

For dev-console changes the drive must be a real browser, not curl. qa/browser is its own Go module (own go.mod), the same pattern as qa/capture, so that chromedp never enters the module that builds the shipped binary. Two ways this bites if you forget it, both checked by hand before writing this down:

  • go test -count=1 ./qa/browser/... run from the repo root does NOT silently pass — it fails outright with directory prefix qa/browser does not contain main module or its selected dependencies. Loud, not a vacuous green, but also not what you wanted.
  • The broad go test -count=1 ./... from the repo root is the quieter trap: it neither errors nor mentions qa/browser at all — it is simply never attempted, and the overall command can still report a clean pass having run zero browser tests.

Run the suite from inside its own module instead:

(cd qa/browser && go test -count=1 ./...)

This builds a fresh binary and drives the chromedp suite against it. Exercising the mechanism is not exercising the integration — a console test that never opens a socket, never sends a frame, and never reads one back has verified nothing a user cares about.

A pending delegation is not a result

If an agent delegates a question to a subagent, its findings may be cited only after that subagent's completion notification has arrived. If a report is due while the delegate is still running, the report says so and marks the claim unknown. An agent cannot distinguish "the delegate answered" from "I know what the delegate would say", and neither can its reader.

Name the instrument for delegated research the way you would for a measurement. "An agent said so" is not an instrument. This is written down because a round asserted what six named web servers do, labelled it verified against primary sources, and had invented all of it while its subagent was still running; the claims reached a merge commit on main before the worker retracted them.

Never commit anything that identifies Joe's machine

Docs get written by running real commands and pasting real output, which is how the tutorial came to ship Joe's actual computer name inside its example transcripts. Use example-host.local, /Users/you/ (or $HOME), and you as placeholders — see docsite/STYLE.md §7 for the full table.

TestNoLocalMachineIdentifiersInRepo (root package) enforces this: it reads the live hostname, home directory and account name of whatever machine is running the tests and fails if any of them appear in a committed file, plus flags any other real-looking /Users/<name> or /home/<name> path. It keys off the current machine deliberately, so it protects whoever drafts next without needing a hardcoded blocklist. Paste output freely while drafting; scrub what the test names before committing. Do not weaken or skip it to get a branch green.

Shared singletons

Parallel agents collide on these. Each agent gets its own, or the run corrupts itself:

  • TCP ports. Always bind port 0 / allocate a free port. A hardcoded 8080 in a test or a manual drive will fight every sibling agent.
  • Unix socket paths. --unixsocket targets go in the agent's own t.TempDir(), never a shared /tmp/websocketd.sock.
  • Headless Chrome user-data-dir. Two chromedp sessions sharing a profile directory crash each other; give each run its own.
  • refs/stash. Shared across every worktree of this repo — never git stash. Use a second worktree or git diff > file instead.
  • The built binary path. qa/integration builds into its own temp dir; don't add a build that writes ./websocketd from two agents at once.

Do not touch

Settled decisions. Reopen only with Joe, and read the DIARY entry first:

  • No global SIGTERM handler for unix-socket cleanup. Rejected: it changes Ctrl+C and exit-code semantics for every user to serve a niche flag. Startup recovery is the only cleanup mechanism (DIARY 2026-08-17).
  • No ReadTimeout/WriteTimeout on the main server. They would kill long-lived WebSocket and streaming-CGI responses. ReadHeaderTimeout only (DIARY 2026-08-17).
  • --maxframesize defaults to 1 MiB; --maxforks to 1024. Both are deliberate backstops, not capacity plans.
  • --origin port matching is strict, with :* as the explicit wildcard (issue #473).
  • --socketmode does not change the default socket permissions (issue #474).
  • SERVER_NAME/SERVER_PORT stay Host-derived. Documented, not "fixed" (issue #475).
  • The default origin policy stays permissive for now; the stderr banner plus --anyorigin is the agreed shape until the announced flip.
  • SECURITY_AUDIT.md stays deleted. Do not reinstate it.
  • website/CNAME must not be deleted or renamed.

Asset locations

Screenshots and recordings of the dev console go in website/img/console/ so the site and the docs share one source. Reference them from README.md and website/index.html rather than duplicating files.

Structure

  • main.go — entry point, flag parsing
  • config.go — configuration types
  • help.go — help text
  • version.go — version info
  • libwebsocketd/ — core library (WebSocket handling, HTTP, process management)
  • examples/ — example scripts in various languages
  • release/ — release/packaging scripts

Download now (ZIP)

Trending

More talk lately.about the whole repository

Dec 2025: 0 mentionsJan 2026: 0 mentionsFeb 2026: 0 mentionsMar 2026: 0 mentionsApr 2026: 0 mentionsMay 2026: 1 mentionJun 2026: 0 mentionsJul 2026: 0 mentionsAug 2026: 0 mentionsSep 2026: 1 mentionOct 2026: 1 mentionNov 2026: 0 mentions
Dec 2025Jun 2026Nov 2026
Mentions a month of the joewalnes/websocketd repository that our weekly crawl found. There is no adoption data for this file yet.

Who's talking about joewalnes/websocketd

The biggest 5 of 38 mentions linking to the repository that our crawl found. Each line is their own headline.

See all 38

Talked about on GitHub, Stack Overflow, Hacker News, the web, YouTube and Bluesky (38 times).

On GitHub · 23

18 more on GitHub

On Stack Overflow · 6

1 more on Stack Overflow

On YouTube · 1

On Bluesky · 1

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.