Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ OpenClaw's own supported commands and both verified against a real OpenClaw 2026
| Repair | Does | Verified by | Risk |
|---|---|---|---|
| `auto-update-enabled-warning` | `openclaw config set update.auto.enabled false` | reads the key back through `config get` | low |
| `gateway-loopback-no-auth` | sets `gateway.auth.mode` to `token`, then `doctor --fix --generate-gateway-token` | reads the mode back; never reads the token itself | medium |
| `gateway-loopback-no-auth` | reuses or generates a token, then sets token mode | verifies mode and token presence without recording token material; client usability remains an external OpenClaw semantic | medium |

Repairable findings went from 1 to 3. On a real install, broken deliberately:

Expand Down
53 changes: 50 additions & 3 deletions cli/adapters/openclaw.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,11 +490,50 @@ export function createOpenClawAdapter({
*
* Repairs verify against this rather than parsing openclaw.json: it is the value OpenClaw
* resolves, and it keeps repair evidence to a single key instead of a whole config blob.
* Returns '' when the key is unset or the call fails — callers must not read that as false.
* An empty value can be a successful "unset" result. Callers must use `ok` to distinguish
* that from an invocation failure.
*/
async configGet(key, options = {}) {
if (typeof key !== 'string' || !/^[A-Za-z0-9_.-]{1,128}$/.test(key)) return '';
return processText(await invoke(['config', 'get', key], options));
if (typeof key !== 'string' || !/^[A-Za-z0-9_.-]{1,128}$/.test(key)) {
return Object.freeze({
ok: false,
value: '',
status: null,
errorSummary: 'invalid config key',
});
}
const result = await invoke(['config', 'get', key], options);
const ok = result.status === 0
&& result.errorCode == null
&& result.errorSummary == null
&& result.signal == null
&& !result.timedOut
&& !result.aborted
&& !result.outputLimitExceeded
&& !result.stdoutTruncated
&& !result.stderrTruncated;
return Object.freeze({
ok,
value: ok ? String(result.stdout || '').trim() : '',
status: result.status,
errorSummary: ok
? null
: result.errorSummary || `openclaw config get exited with status ${result.status}`,
});
},

/**
* Check whether a config key has a non-empty value without returning that value to callers.
* This is the only repair-facing primitive allowed for secret-bearing config keys.
*/
async configHasValue(key, options = {}) {
const read = await this.configGet(key, options);
return Object.freeze({
ok: read.ok,
present: read.ok ? read.value.trim().length > 0 : false,
status: read.status,
errorSummary: read.errorSummary,
});
},

/** Set one config key. Values are passed as literal argv, never through a shell. */
Expand All @@ -504,6 +543,14 @@ export function createOpenClawAdapter({
}
return invoke(['config', 'set', key, String(value)], options);
},

/** Remove one config key through OpenClaw itself. */
async configUnset(key, options = {}) {
if (typeof key !== 'string' || !/^[A-Za-z0-9_.-]{1,128}$/.test(key)) {
return Object.freeze({ status: 1, errorSummary: 'invalid config key' });
}
return invoke(['config', 'unset', key], options);
},
/**
* PIDs that plausibly belong to a running gateway *server*.
*
Expand Down
Loading