Summary
I used Tool Bridge to deploy Home Assistant, an official Xiaomi integration, Docker Compose services, and a Cloudflare Tunnel on a remote Ubuntu device without using SSH directly.
The device connection was stable and the deployment succeeded. I did not observe dropped commands or unexplained offline transitions. The core Linux-device path is genuinely useful.
The main friction is that the only general process interface is an arbitrary shell command:
device/<linux>/shell/exec
effect: destructive
confirm: true
scope: call
input: { command: string, cwd?: string, timeoutMs?: number }
result: { stdout, stderr, exitCode }
This is a correct conservative classification for arbitrary shell, but it is too coarse to be the primary agent operations layer. Read-only inspection and high-impact mutations look identical to the caller, output is unstructured, and long-running work has no observable lifecycle beyond a timeout and final stdout/stderr.
Environment
tb CLI: 0.25.0
- Gateway: 0.17.0
- Device: x86_64 Ubuntu 26.04 LTS
- Device connection: Linux daemon / WebSocket
- Shell exposure: explicit
allowed commands: *
- Device URL, credentials, usernames, domain names, and Home Assistant data are intentionally omitted
What worked
Through the device connection I was able to:
- inspect OS, CPU, memory, disk, Docker, and Compose state;
- create deployment directories and configuration;
- clone and install the upstream Home Assistant integration;
- start, stop, and restart Docker Compose services;
- inspect container health and logs;
- configure and verify a secure tunnel;
- repeat state checks after mutations.
The connection remained available throughout the deployment.
The device also exposes a structured filesystem context at device/<linux>/fs. That is a useful existing capability, not a missing feature. It supports list/get/write/update/delete/search and optimistic concurrency via ifVersion.
Friction observed
1. Read-only and mutating shell commands have the same contract
All of these are represented as the same destructive, confirming tool:
uname -a
docker ps
docker compose ps
docker compose up -d
docker compose down
# and any arbitrary file/process mutation
This creates two bad choices for an agent workflow:
- request confirmation for every harmless diagnostic read; or
- obtain broad authorization for a sequence where the live contract cannot distinguish inspection from mutation.
The answer should not be heuristic parsing of shell strings. Shell grammar, aliases, redirections, scripts, and subprocesses make reliable effect inference unrealistic.
A safer direction is to retain arbitrary shell/exec as destructive while allowing the device to expose predeclared structured commands with explicit schemas and effects, for example:
- system information / disk / process / Docker status as
effect: read;
- service restart / Compose apply as
effect: write;
- deletion / destructive lifecycle operations as
effect: destructive, confirm: true.
The current daemon already requires explicit shell allowlisting. Extending the exposure model with per-command metadata or structured argv operations would preserve that safety boundary.
2. The result is final raw text, not an operation lifecycle
The only result is stdout, stderr, and exitCode. The schema does not expose:
commandId / operation identity;
- started/completed timestamps;
- live stdout/stderr streaming;
- output truncation metadata;
- poll/status/cancel;
- whether a timeout killed the process;
- reconnect/resume behavior.
Docker pulls, image starts, log inspection, and package/integration setup can take long enough that an agent needs observable progress. In this deployment I worked around this by keeping commands bounded and issuing separate verification commands. No command timed out, but the interface made long-running steps harder to supervise than necessary.
3. Every mutation needs a second verification call
Raw shell success only proves a process exit code. It does not prove the intended deployment state.
After each meaningful operation I had to run additional commands such as Compose status, container logs, HTTP checks, or file reads. This is appropriate validation, but common high-level operations could return structured postconditions rather than forcing every caller to rebuild them from text.
4. Filesystem support is useful but incomplete as a configuration workflow
The existing device/<linux>/fs surface is better than shell redirection:
write is an idempotent full replace;
update supports optimistic ifVersion;
delete is explicitly destructive.
However:
- live help currently omits
effect for list/get/write/update even though their scopes are read/write;
update replaces content rather than applying a line/hunk patch;
- there is no atomic multi-file change set;
- multi-line Compose/YAML changes still make shell-based editing tempting.
An explicit patch operation with ifVersion, plus an optional atomic batch for several configuration files, would make configuration changes safer and easier to review.
5. Shell has no SecretStore injection boundary
The shell schema accepts only a command string, cwd, and timeout. It has no way to request a SecretStore reference as an environment variable without materializing the secret in device configuration, a command, or an intermediate file.
Arbitrary stdout/stderr also cannot guarantee that a process will not print credentials. During this deployment I had to deliberately avoid commands that displayed token, certificate, or tunnel credential contents.
A first-class secretRefs -> env mapping should:
- resolve secrets at execution time;
- avoid placing values in argv, help, audit text, or result payloads;
- redact exact injected values from stdout/stderr;
- expire or zero the injected process environment after the operation.
Suggested direction
Keep arbitrary shell dangerous
- Preserve
shell/exec as destructive + confirm:true.
- Keep default shell exposure disabled.
- Do not infer safety from command text.
Add a structured command exposure profile
Allow Linux device owners to expose named operations with:
- executable + argv schema (no implicit shell unless explicitly requested);
- cwd and environment policy;
effect, confirm, timeout, output limits;
- optional structured result parser/postcondition;
- per-operation allowlist and audit identity.
Add observable execution
For long-running operations:
- return an operation/command ID immediately;
- provide status, streaming output, cancel, deadline, and terminal result;
- distinguish timeout, cancellation, process signal, disconnect, and normal exit;
- treat a disconnected or timed-out mutation as outcome unknown unless a terminal state can be recovered.
Extend the existing filesystem context
- emit explicit read/write effects for list/get/write/update;
- add patch-with-
ifVersion;
- consider atomic multi-file batches;
- keep path confinement and no-follow protections.
Add SecretStore-backed environment injection
- pass only secret references through the control plane;
- inject values directly into the child environment;
- redact injected values from logs/results;
- never persist them in device exposure config or command history.
Suggested acceptance criteria
- A device can expose at least one structured read-only system command with
effect: read and no destructive confirmation.
- Arbitrary
shell/exec remains destructive and cannot be downgraded by command-name heuristics.
- A long-running command can be started, observed, cancelled, and resolved to a terminal state by ID.
- Timeout/cancel/disconnect outcomes are machine-readable and safe retry policy is explicit.
- Filesystem read/write tools expose correct effect metadata.
- A file patch can be guarded by the current
ifVersion.
- A secret can be injected by reference without appearing in argv, logs, results, or persistent device config.
- Existing Linux daemon shell/FS exposure remains backward compatible.
Related historical context: #71 and #72 discuss device-call observability and CLI round trips on mobile. This issue is specifically about the Linux arbitrary-shell operations layer observed during a successful real deployment.
Summary
I used Tool Bridge to deploy Home Assistant, an official Xiaomi integration, Docker Compose services, and a Cloudflare Tunnel on a remote Ubuntu device without using SSH directly.
The device connection was stable and the deployment succeeded. I did not observe dropped commands or unexplained offline transitions. The core Linux-device path is genuinely useful.
The main friction is that the only general process interface is an arbitrary shell command:
This is a correct conservative classification for arbitrary shell, but it is too coarse to be the primary agent operations layer. Read-only inspection and high-impact mutations look identical to the caller, output is unstructured, and long-running work has no observable lifecycle beyond a timeout and final stdout/stderr.
Environment
tbCLI: 0.25.0allowed commands: *What worked
Through the device connection I was able to:
The connection remained available throughout the deployment.
The device also exposes a structured filesystem context at
device/<linux>/fs. That is a useful existing capability, not a missing feature. It supports list/get/write/update/delete/search and optimistic concurrency viaifVersion.Friction observed
1. Read-only and mutating shell commands have the same contract
All of these are represented as the same destructive, confirming tool:
uname -a docker ps docker compose ps docker compose up -d docker compose down # and any arbitrary file/process mutationThis creates two bad choices for an agent workflow:
The answer should not be heuristic parsing of shell strings. Shell grammar, aliases, redirections, scripts, and subprocesses make reliable effect inference unrealistic.
A safer direction is to retain arbitrary
shell/execas destructive while allowing the device to expose predeclared structured commands with explicit schemas and effects, for example:effect: read;effect: write;effect: destructive, confirm: true.The current daemon already requires explicit shell allowlisting. Extending the exposure model with per-command metadata or structured argv operations would preserve that safety boundary.
2. The result is final raw text, not an operation lifecycle
The only result is
stdout,stderr, andexitCode. The schema does not expose:commandId/ operation identity;Docker pulls, image starts, log inspection, and package/integration setup can take long enough that an agent needs observable progress. In this deployment I worked around this by keeping commands bounded and issuing separate verification commands. No command timed out, but the interface made long-running steps harder to supervise than necessary.
3. Every mutation needs a second verification call
Raw shell success only proves a process exit code. It does not prove the intended deployment state.
After each meaningful operation I had to run additional commands such as Compose status, container logs, HTTP checks, or file reads. This is appropriate validation, but common high-level operations could return structured postconditions rather than forcing every caller to rebuild them from text.
4. Filesystem support is useful but incomplete as a configuration workflow
The existing
device/<linux>/fssurface is better than shell redirection:writeis an idempotent full replace;updatesupports optimisticifVersion;deleteis explicitly destructive.However:
effectfor list/get/write/update even though their scopes are read/write;updatereplaces content rather than applying a line/hunk patch;An explicit patch operation with
ifVersion, plus an optional atomic batch for several configuration files, would make configuration changes safer and easier to review.5. Shell has no SecretStore injection boundary
The shell schema accepts only a command string, cwd, and timeout. It has no way to request a SecretStore reference as an environment variable without materializing the secret in device configuration, a command, or an intermediate file.
Arbitrary stdout/stderr also cannot guarantee that a process will not print credentials. During this deployment I had to deliberately avoid commands that displayed token, certificate, or tunnel credential contents.
A first-class
secretRefs -> envmapping should:Suggested direction
Keep arbitrary shell dangerous
shell/execasdestructive + confirm:true.Add a structured command exposure profile
Allow Linux device owners to expose named operations with:
effect,confirm, timeout, output limits;Add observable execution
For long-running operations:
Extend the existing filesystem context
ifVersion;Add SecretStore-backed environment injection
Suggested acceptance criteria
effect: readand no destructive confirmation.shell/execremains destructive and cannot be downgraded by command-name heuristics.ifVersion.Related historical context: #71 and #72 discuss device-call observability and CLI round trips on mobile. This issue is specifically about the Linux arbitrary-shell operations layer observed during a successful real deployment.