Device Prep and Runtime
Purpose
Define what must be true before a skill run is reliable: device readiness, wrapper environment, timeout behavior, multi-device targeting, and output capture.
Sources
- Skill runtime:
apps/node/src/domain/skills/runSkill.ts - Skill config:
apps/node/src/domain/skills/skillsConfig.ts - CLI skill wrapper:
apps/node/src/cli/commands/skills.ts - Environment variables overview:
docs/api/environment.md
Device Prep Checklist
Before clawperator skills run, the target should already satisfy the normal Clawperator readiness path:
- device visible to adb
- expected Operator package installed
- accessibility service running
- version compatibility healthy
Recommended verification:
clawperator doctor --json --device <device_serial>
Treat the device as ready only when:
- exit code
0 criticalOk == true
Verification pattern:
- run
clawperator doctor --json --device <device_serial> --operator-package <package> - confirm
report.operatorPackagematches the package you plan to use inside the skill - confirm the doctor report is for the same
deviceIdyou plan to pass toskills run
For first-time setup, use Setup. For runtime recovery, use Operator App.
Runtime Environment Passed To Skill Scripts
The wrapper injects these environment variables:
| Variable | Meaning |
|---|---|
CLAWPERATOR_BIN |
command the skill should use when it needs to invoke Clawperator |
CLAWPERATOR_OPERATOR_PACKAGE |
Operator package the skill should target on its internal CLI calls |
Resolution behavior:
CLAWPERATOR_BINuses explicit env override first, then a local sibling build, then globalclawperatorCLAWPERATOR_OPERATOR_PACKAGEuses the explicit wrapper flag first, then environment, thencom.clawperator.operator
The wrapper also preserves the rest of process.env when spawning the child process.
The exact CLAWPERATOR_BIN resolution order from resolveSkillBin() is:
- non-empty
CLAWPERATOR_BIN - branch-local sibling build at
apps/node/dist/cli/index.jswhen it exists - global
clawperator
The exact CLAWPERATOR_OPERATOR_PACKAGE resolution order from cmdSkillsRun() plus resolveOperatorPackage() is:
--operator-package <pkg>onclawperator skills run- non-empty
CLAWPERATOR_OPERATOR_PACKAGE com.clawperator.operator
Verification pattern:
CLAWPERATOR_OPERATOR_PACKAGE=com.clawperator.operator.dev \
clawperator skills run com.android.settings.capture-overview --json
CLAWPERATOR_OPERATOR_PACKAGE=com.clawperator.operator.dev \
clawperator skills run com.android.settings.capture-overview --operator-package com.clawperator.operator --json
Then verify the skill's internal clawperator calls behave against the intended package. When you need stronger confirmation, add a deliberate internal probe inside the skill script and inspect the raw output.
How Device Id Is Passed
runSkill() itself does not invent a device id. The CLI wrapper decides argument passing.
Current skills run behavior:
- if
--device <serial>is present, the wrapper prepends that serial as the first child argument - then it appends any forwarded args after
--
That means most scripts should expect:
argv[2] = <device_serial>
when they are run through the wrapper with explicit device targeting.
Verification pattern:
clawperator skills run com.example.app.do-thing --device <device_serial> --json -- --mode smoke
Confirm that your script receives:
- first positional child argument:
<device_serial> - remaining forwarded args after that:
--mode,smoke
If --device is omitted, the wrapper passes no synthetic device argument at all.
Timeout Behavior
Default skill timeout:
120000milliseconds
This comes from runSkill.ts and applies to the subprocess wrapper, not to any single Clawperator action inside the skill.
Override it with:
clawperator skills run <skill_id> --timeout 90000
If the child does not exit in time:
- the wrapper sends
SIGTERM - the command fails with
SKILL_EXECUTION_TIMEOUT
Top-level timeout parsing failures happen before the skill starts:
{
"code": "EXECUTION_VALIDATION_FAILED",
"message": "timeoutMs must be a finite number"
}
{
"code": "USAGE",
"message": "--timeout requires a value"
}
Verification pattern:
clawperator skills run com.android.settings.capture-overview --timeout 3210 --json
Check:
timeoutMsis3210in the success payload- if you omit
--timeout, the JSON payload does not echotimeoutMs, but the wrapper still uses the internal default120000
Multi-Device Skill Execution
When more than one device is connected:
- always pass
--device <serial>
Example:
clawperator skills run com.android.settings.capture-overview --device <device_serial>
Why:
- the wrapper forwards the selected device id into the child script
- the child script can then pass that same serial into its internal Clawperator calls
Without explicit targeting, skill behavior depends on what the script itself does. The wrapper does not auto-add a device argument unless one was provided.
Related failure mode:
- if the child script later invokes Clawperator without a device while multiple devices are connected, the nested call can fail with
MULTIPLE_DEVICES_DEVICE_ID_REQUIRED
Recovery:
- pass
--device <serial>on the outerskills run - ensure the child script forwards that positional device id into its own internal Clawperator calls
Output and Logging
runSkill() captures:
- stdout
- stderr
- exit code
- total duration
Success wrapper fields:
| Field | Meaning |
|---|---|
skillId |
invoked registry id |
output |
raw stdout |
exitCode |
child exit code |
durationMs |
total wrapper runtime |
Failure wrapper fields may also include:
| Field | Meaning |
|---|---|
stdout |
partial stdout captured before failure |
stderr |
partial stderr captured before failure |
exitCode |
non-zero child exit code when available |
In pretty mode, the CLI also prints a one-line banner with:
- CLI version
- APK status
- log path
- docs hint
That banner is a convenience layer from cmdSkillsRun(), not part of the JSON wrapper contract.
The pretty banner contains these exact components:
- CLI version from
getCliVersion() - APK status derived from
checkApkPresence() - log path, defaulting to
~/.clawperator/logs/clawperator-YYYY-MM-DD.logwhenlogger.logPath()did not supply an override - hint:
tail -f <logPath> - docs hint:
https://docs.clawperator.com/llms.txt
Verification pattern:
- use
--jsonwhen another tool needs parseable output - use pretty mode when a human operator wants the banner plus streamed skill output
- if pretty mode shows an APK warning or failure, fix the package/device setup before assuming the skill logic is wrong
Debugging skill runs with logs:
# Stream logs in real time while a skill runs
clawperator logs
# In another terminal:
clawperator skills run <skill_id> --device <device_serial> --operator-package <package>
The unified logger captures skill output as skills.run.output events, enabling post-timeout diagnostics. See Logging for details.
Runtime Success Example
{
"status": "success",
"skillId": "com.android.settings.capture-overview",
"output": "RESULT|status=success|snapshot=/tmp/settings.xml\n",
"skillResult": null,
"exitCode": 0,
"durationMs": 15321
}
Runtime Indeterminate Example
{
"status": "indeterminate",
"code": "SKILL_VERIFICATION_INDETERMINATE",
"message": "Declared verification was not proved.",
"skillId": "com.android.settings.capture-overview",
"output": "[Clawperator-Skill-Result]\n{\"status\":\"success\"}\n",
"skillResult": {
"status": "success"
},
"exitCode": 0,
"durationMs": 15321
}
This wrapper-level indeterminate state means the skill process ran without an
upstream runtime failure, but the declared skill.json verification contract
was not proved. The parsed skillResult is returned verbatim; the wrapper does
not rewrite it.
Runtime Failure Example
{
"status": "failed",
"code": "SKILL_EXECUTION_TIMEOUT",
"message": "Skill com.android.settings.capture-overview timed out after 120000ms",
"skillId": "com.android.settings.capture-overview",
"stdout": "RESULT|status=partial\n",
"stderr": "still waiting for target node\n",
"skillResult": null
}
Another common failure is a bad registry or missing script:
{
"code": "REGISTRY_READ_FAILED",
"message": "Registry not found at configured path: /tmp/missing-registry.json. The installed registry normally lives at ~/.clawperator/skills/skills/skills-registry.json. Fix CLAWPERATOR_SKILLS_REGISTRY, unset it to use the installed copy, then rerun clawperator skills list --json, or run clawperator skills install."
}
{
"code": "SKILL_SCRIPT_NOT_FOUND",
"message": "Script not found: /abs/path/to/skills/com.android.settings.capture-overview/scripts/run.js",
"skillId": "com.android.settings.capture-overview"
}
{
"code": "SKILL_NOT_FOUND",
"message": "Skill not found: com.android.settings.capture-overview",
"skillId": "com.android.settings.capture-overview"
}
Recovery patterns:
REGISTRY_READ_FAILED: runclawperator skills installto restore the registry at the installed home path (~/.clawperator/skills/skills/skills-registry.json); ifCLAWPERATOR_SKILLS_REGISTRYis set to a custom path, fix or unset that variableSKILL_NOT_FOUND: confirm the exact registryidwithclawperator skills list --jsonSKILL_SCRIPT_NOT_FOUND: repair the registry entry or restore the script file on diskSKILL_EXECUTION_FAILED: inspectexitCode,stdout, andstderrSKILL_EXECUTION_TIMEOUT: inspect partialstdoutand only then consider increasing--timeoutSKILL_RESULT_PARSE_FAILED: fix malformed framed output or unreadable trusted source metadata inskill.json
Practical Runtime Rules
- gate device readiness with
doctorbefore blaming the skill - pass
--deviceexplicitly in multi-device environments - pass
--operator-package com.clawperator.operator.devfor local debug APK workflows - prefer
--jsonfor machine-consumed skill runs so the pretty banner does not pollute stdout - inspect partial stdout and stderr on failures before rerunning blindly