The apps entry and the launch
This page covers the hub side of an app: the entry in config.json, how the hub starts the app, and what the app server must do. For the full contract, see Hub protocol v1.
The apps list
Section titled “The apps list”The apps list in config.json (in the hub folder, ~/.claude-hub by default) sets which apps run and in which order.
{ "apps": [ { "id": "board", "path": "C:/dev/board" }, { "id": "kanban", "enabled": false }, { "id": "cost", "port": 4600 } ]}| Field | Type | Meaning |
|---|---|---|
id |
string | The app id. A built-in id (kanban, marketplace, cost, memory) or a new id that matches ^[a-z][a-z0-9-]{0,31}$ |
path |
string | Optional. The app folder, the one with hub-app.json. Absolute, or relative to the folder of config.json. The hub does not expand ~ |
enabled |
boolean | Optional. false turns the app off: no process, no tab, no port, and its actions and capabilities go to no one |
port |
integer | Optional. The proxy port, 1 to 65535. See Ports |
- Order. The listed apps come first, in list order. The built-in apps that are not listed follow, in the default order:
kanban,marketplace,cost,memory. The tab order is also the order of Alt+1 to Alt+9. - Replace. An entry with a built-in id and a
pathruns that folder in place of the built-in app. - Add. An entry with a new id must have a
path. Without one, the hub logsUnknown app "<id>" ... has no "path", ignored. - Id match. The
idin the folder’shub-app.jsonmust be the entry’sid. Otherwise the hub skips the app. - First wins. When two enabled apps declare the same capability, action or port, the first in tab order gets it. The hub logs each case at startup, for example:
kanban-next and kanban both declare capability "terminal"; kanban-next has it. Disable one of them. - Restart. The hub reads the list at startup only.
Startup log lines
Section titled “Startup log lines”| Line | Cause |
|---|---|
<id> runs from <folder> |
The entry has a path. Shows the resolved folder |
<folder>/hub-app.json: not found. <id> is skipped |
No manifest in the folder |
<folder>/hub-app.json: <reason>. <id> is skipped |
The manifest is not valid. See Manifest rules |
<id>: action "<name>" <reason>, skipped |
One action is not valid. The rest of the app runs |
... both declare ... |
A conflict. See rule 5 |
Every app is disabled ... |
Every entry has "enabled": false. The hub stops |
Launch
Section titled “Launch”For each config dir, the hub starts each enabled app once:
- It runs
run.entryfrom the manifest with the hub’s own Node, with--max-http-header-size=65536. - The working directory is the hub’s folder, not the app’s. Resolve your files from
__dirname. - It gives the app an IPC channel.
- When the app exits, the hub starts it again, up to 5 times in 60 seconds.
While the app is starting or stopped, its tab shows an error page that names the state. An app that prints no ready line in 20 s stays in Starting: the hub stops waiting for it, and takes the port if the line comes later.
Environment
Section titled “Environment”| Variable | Value |
|---|---|
CLAUDE_HUB |
1 |
HUB_URL |
The hub origin, http://localhost:<hub port> |
CLAUDE_CONFIG_DIR |
The Claude config dir of this set of apps. Read it once at start |
PORT |
0: listen on any free port |
HOST, ALLOWED_HOSTS |
The hub’s bind address and extra host names. Bind to HOST and accept the Host headers that the hub accepts |
HUB_SDK_SERVER |
The absolute path of the hub’s server SDK |
CCK_TERMINAL, CCK_TERMINAL_TOKEN |
Only for the terminal provider, when the terminal is on. The terminal config as JSON, and the token that the terminal WebSocket must check |
The app gets the rest of the hub’s environment too.
What the app server must do
Section titled “What the app server must do”-
Mount the SDK first. Before it serves static files:
if (process.env.HUB_SDK_SERVER) require(process.env.HUB_SDK_SERVER).mount(app);mount(app)takes an Express app. It addsGET /hub-configandGET /vendor/claude-hub-sdk.js, answers the hub’s ping on the IPC channel, and exits the process when the hub goes away. -
Print the ready line. When the server listens, print one stdout line with the real port, then a newline:
Board running at http://localhost:52814The hub matches
/running at http:\/\/localhost:(\d+)/i. Print no other line that matches. The hub waits up to 20 s for the ready lines. -
Allow framing. Do not send
X-Frame-Optionsor aframe-ancestorsrule that blocks the hub origin. -
Work alone. The app must run with no hub. Everything in the SDK turns on only under the hub.
The browser sees each app on a proxy port, not on the port the app listens on. The proxy port is the app’s origin, so it keys the app’s localStorage. The hub takes the first value that is set:
- The
--<id>-portflag. - The entry’s
port. run.defaultPortfrom the manifest.
When that port is busy, the hub uses a random port for this run, and the app starts with empty localStorage. Give each app a defaultPort that no other app uses.
The proxy removes the hub’s hub_token cookie from each request before it forwards it.