Problem Description
When an agent whose harness configuration is registered on the Hub (and not on the broker's local filesystem) is restarted (via StartAgent), the broker fails with:
Failed to dispatch to runtime broker: runtime broker returned error 500: {"error":{"code":"runtime_error","message":"Failed to start agent: failed to find harness-config \"claude\": harness-config \"claude\" not found"}}
Even though:
- The harness configuration (
claude, opencode, etc.) exists and is active on the Hub.
- Freshly created agents in the exact same project on the exact same broker start successfully.
- The stored
appliedConfig on the failing agent is valid and contains the correct harnessConfigId and harnessConfigHash.
Root Cause Analysis
There is a structural divergence between the Create/Dispatch path and the Start/Restart path in the runtime broker:
-
Create path (pkg/runtimebroker/handlers.go:675-694):
- The Hub sends the full
CreateAgentConfig, including HarnessConfigID and HarnessConfigHash.
buildStartContext invokes hydrateHarnessConfig (handlers.go:992), which fetches and caches the harness-config from the Hub using the ID/hash.
opts.HarnessConfigPath is populated on the context, so resolveHarnessConfigDir (pkg/agent/provision.go:411) resolves it successfully.
-
Start/Restart path (pkg/runtimebroker/handlers.go:1291-1350):
startReq (handlers.go:1302) only receives HarnessConfig string (the name, e.g. "claude"), and is missing HarnessConfigID and HarnessConfigHash.
- The hub-side dispatcher signature
StartAgent (pkg/hub/httpdispatcher.go:65) only sends the harness name string, even though the Hub has access to agent.AppliedConfig.HarnessConfigID and agent.AppliedConfig.HarnessConfigHash.
- Because ID and Hash are empty in
cfg, hydrateHarnessConfig early-returns empty at handlers.go:993:
if cfg == nil || (cfg.HarnessConfigID == "" && cfg.HarnessConfigHash == "") {
return "", nil
}
- With
opts.HarnessConfigPath unset, resolveHarnessConfigDir falls back to the on-disk search FindHarnessConfigDir (pkg/config/harness_config.go:105-150), searching only template/project/global local paths on the broker node. On brokers without a local ~/.scion/harness-configs/<name>/ directory, this returns harness-config %q not found (pkg/agent/provision.go:756).
Proposed Fix
-
Broker Start Request (pkg/runtimebroker/handlers.go):
- Add
HarnessConfigID string json:"harnessConfigId,omitempty"andHarnessConfigHash string json:"harnessConfigHash,omitempty" to startReq (handlers.go:1302).
- Populate
cfg.HarnessConfigID and cfg.HarnessConfigHash when building CreateAgentConfig in startAgent (handlers.go:1339).
-
Hub Client & Dispatcher Transport (pkg/hub/httpdispatcher.go, controlchannel_client.go, brokerclient.go):
- Update
StartAgent signatures and payloads to pass harnessConfigID and harnessConfigHash.
- In
httpdispatcher.go, read agent.AppliedConfig.HarnessConfigID and agent.AppliedConfig.HarnessConfigHash when calling StartAgent.
A PR with this fix and regression tests is in preparation.
Problem Description
When an agent whose harness configuration is registered on the Hub (and not on the broker's local filesystem) is restarted (via
StartAgent), the broker fails with:Even though:
claude,opencode, etc.) exists and isactiveon the Hub.appliedConfigon the failing agent is valid and contains the correctharnessConfigIdandharnessConfigHash.Root Cause Analysis
There is a structural divergence between the Create/Dispatch path and the Start/Restart path in the runtime broker:
Create path (
pkg/runtimebroker/handlers.go:675-694):CreateAgentConfig, includingHarnessConfigIDandHarnessConfigHash.buildStartContextinvokeshydrateHarnessConfig(handlers.go:992), which fetches and caches the harness-config from the Hub using the ID/hash.opts.HarnessConfigPathis populated on the context, soresolveHarnessConfigDir(pkg/agent/provision.go:411) resolves it successfully.Start/Restart path (
pkg/runtimebroker/handlers.go:1291-1350):startReq(handlers.go:1302) only receivesHarnessConfig string(the name, e.g."claude"), and is missingHarnessConfigIDandHarnessConfigHash.StartAgent(pkg/hub/httpdispatcher.go:65) only sends the harness name string, even though the Hub has access toagent.AppliedConfig.HarnessConfigIDandagent.AppliedConfig.HarnessConfigHash.cfg,hydrateHarnessConfigearly-returns empty athandlers.go:993:opts.HarnessConfigPathunset,resolveHarnessConfigDirfalls back to the on-disk searchFindHarnessConfigDir(pkg/config/harness_config.go:105-150), searching only template/project/global local paths on the broker node. On brokers without a local~/.scion/harness-configs/<name>/directory, this returnsharness-config %q not found(pkg/agent/provision.go:756).Proposed Fix
Broker Start Request (
pkg/runtimebroker/handlers.go):HarnessConfigID stringjson:"harnessConfigId,omitempty"andHarnessConfigHash stringjson:"harnessConfigHash,omitempty"tostartReq(handlers.go:1302).cfg.HarnessConfigIDandcfg.HarnessConfigHashwhen buildingCreateAgentConfiginstartAgent(handlers.go:1339).Hub Client & Dispatcher Transport (
pkg/hub/httpdispatcher.go,controlchannel_client.go,brokerclient.go):StartAgentsignatures and payloads to passharnessConfigIDandharnessConfigHash.httpdispatcher.go, readagent.AppliedConfig.HarnessConfigIDandagent.AppliedConfig.HarnessConfigHashwhen callingStartAgent.A PR with this fix and regression tests is in preparation.