ruvnet / ruvnet/ruflo

config: `config_set` writes under a top-level `values` envelope that the daemon's config.json reader never unwraps, so `config_set daemon.idleSecs 0` cannot reach the daemon

Open
#3,192 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
72.6k
Forks
8.6k
Avg merge
3d 3h
Merged PRs (30d)
85

Description

# config: `config_set` writes under a top-level `values` envelope that the daemon's config.json reader never unwraps, so `config_set daemon.idleSecs 0` cannot reach the daemon

**Environment**
- ruflo 3.38.19
- node v24.6.0
- macOS 26.6.2
- agentdb 3.0.0-alpha.20

**Steps to reproduce**
1. `config_set` (MCP tool) `daemon.idleSecs` to `0` with default scope.
2. Inspect the resulting `.claude-flow/config.json`.
3. Start the daemon and check whether the idle-shutdown limit changed.

**Observed**
`config_set` writes:
```json
{
"values": { "daemon.idleSecs": 0 },
"scopes": {},
"version": "3.0.0",
"updatedAt": "..."
}
```
The daemon's own config loader reads the *top level* of the same file for the flat key, or `scopes.project`, never `values`:
```js
const cfg = raw?.scopes?.project ?? raw;
const rawIdle = cfg['daemon.idleSecs'] ?? raw['daemon.idleSecs'];
```
`raw['daemon.idleSecs']` is `undefined` (the real value lives at `raw.values['daemon.idleSecs']`), and `raw.scopes.project` is also empty (the write went to `raw.values`, not `raw.scopes.project`, because `config_set` was called with default scope). So the write silently has no effect on the daemon.

**Expected**
A value written through the config door (`config_set`) that shares a key name with what the daemon loader reads (`daemon.idleSecs`) should reach the daemon — canon's own two components read and write the same physical file but disagree on the file's shape.

**Root cause — corrected from the initial estate hypothesis**
The estate's working theory going in was that `config_set` calls a `setNestedValue()` helper that expands a dotted key into a nested object (`{"daemon":{"idleSecs":0}}`), and that *this* nesting was the mismatch. That is not what happens: `setNestedValue()` is defined in `config-tools.ts`/`.js` but is **not called anywhere** in that file — it is dead code. The real handler for `config_set` with default scope does a flat assignment with the dotted key left intact as one string, and the mismatch is the `values` **envelope**, not object nesting.

Upstream at pinned sha `db4991967c45c6f72133dff0bb80b0a492960fc1`, `v3/@claude-flow/cli/src/mcp-tools/config-tools.ts`:
```
101: function setNestedValue(obj: Record, key: string, value: unknown): void {
```
(only definition of `setNestedValue` in the file — confirmed via `grep -n "setNestedValue("`, one hit).
```
194: const store = loadConfigStore();
195: const key = input.key as string;
196: const value = input.value;
197: const scope = (input.scope as string) || 'default';
198:
199: const previousValue = store.values[key];
200:
201: if (scope === 'default') {
202: store.values[key] = value;
203: } else {
204: if (!store.scopes[scope]) {
205: store.scopes[scope] = {};
206: }
207: store.scopes[scope][key] = value;
208: }
```
(shown as :198-207 above; installed dist has the identical assignment at `@claude-flow/cli/dist/src/mcp-tools/config-tools.js` :174-181 — `const previousValue = store.values[key];` at :178, `store.values[key] = value;` at :180 — and defines `setNestedValue` unused at :84, matching the upstream shape.)

The daemon's flat-key reader, upstream `v3/@claude-flow/cli/src/services/worker-daemon.ts` (comment references `#2356`):
```
546: // #2356 — lifecycle limits are configured in SECONDS in config.json
547: // (`daemon.ttlSecs` / `daemon.idleSecs`) for parity with the CLI flag
549: const rawTtl = cfg['daemon.ttlSecs'] ?? raw['daemon.ttlSecs'];
550: const rawIdle = cfg['daemon.idleSecs'] ?? raw['daemon.idleSecs'];
```
Installed dist, `@claude-flow/cli/dist/src/services/worker-daemon.js`:
```
396: // #2356 — lifecycle limits are configured in SECONDS in config.json
397: // (`daemon.ttlSecs` / `daemon.idleSecs`) for parity with the CLI flag
399: const rawTtl = cfg['daemon.ttlSecs'] ?? raw['daemon.ttlSecs'];
400: const rawIdle = cfg['daemon.idleSecs'] ?? raw['daemon.idleSecs'];
```
Both `cfg` (`raw?.scopes?.project ?? raw`) and the bare `raw` fallback look at the file's top level or `scopes.project` — neither ever looks under `values`, which is where `config_set`'s default-scope path actually puts the value.

**Suggested fix (sketch)**
Either side closes the gap; smallest change is on the daemon reader, since `values` is `config_set`'s well-established default-scope location and other config consumers may already expect it there:
```diff
- const cfg = raw?.scopes?.project ?? raw;
+ const cfg = raw?.scopes?.project ?? raw?.values ?? raw;
const rawIdle = cfg['daemon.idleSecs'] ?? raw['daemon.idleSecs'];
```
Alternatively, `config_set` could write default-scope values at the file's top level instead of under `values`, but that risks colliding with the `scopes`/`version`/`updatedAt` keys the store file already uses at that level, so the reader-side fix above is the lower-risk one.

**Related**
- #2356 (introduces the flat `daemon.ttlSecs`/`daemon.idleSecs` keys the daemon reads)
- Companion issue `DAEMON-idle-check-restored-lastRun.md` — `daemon.idleSecs: 0` is the workaround for that bug, and this defect is why the workaround cannot currently be applied through the config door at all (a hand-edited `config.json` with the key at top level does work, since it bypasses `config_set`'s envelope).

**Evidence / estate provenance**
`task/task-1788565562107-hrvg6b/receipts` (ns `final`), SCOPE NOTE paragraph — this is where the estate discovered the mismatch operationally (had to hand-edit `config.json` with a flat top-level key rather than use `config_set`); cross-referenced against `task/task-1788563164755-ticrih/recon-c5-sona-hnsw` D-C. The `setNestedValue`-is-dead-code correction above was verified directly against both the pinned-sha upstream source and the installed dist for this filing, not carried over unverified from the estate's package brief.

Contributor guide

Open the contributing guide

Research direction

Read v3/@claude-flow/cli/src/mcp-tools/config-tools.ts and v3/@claude-flow/cli/src/services/worker-daemon.ts, then reproduce the default-scope config_set flow described here. Compare the source with the installed dist counterparts and verify that a default-scope daemon.idleSecs value is consumed by the daemon without breaking scoped configuration.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
backend, cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.