fix: pin writable HOME for CLI dispatch so scheduled kimaki sends survive WP-cron (#228)#239
Merged
Conversation
…vive WP-cron The scheduled "EC Agent Progress Ping" dispatch failed with `EACCES mkdir /var/www/.kimaki` because the CLI dispatch transport inherited the caller's HOME. Under the WP-cron heartbeat (PHP-FPM as www-data, HOME=/var/www, root-owned and unwritable) kimaki could not create its data dir, so every scheduled send died with exit 64 — while a manual run as the opencode user (writable HOME) succeeded. This is the dispatch-config twin of the systemd-unit HOME fix (#233): the unit path already sets Environment=HOME=$SERVICE_HOME + KIMAKI_DATA_DIR, but the installer-written channel block omitted env entirely, so the generic transport fell back to inheriting the parent env. Fixed in both layers for durability (the installer half is what survives upgrades, since each upgrade rewrites the marker block): 1. Installer (config): lib/cli-channel.sh gains an optional env_json arg that renders an 'env' => [ ... ] line into the channel block; bridges/kimaki.sh stamps HOME + KIMAKI_DATA_DIR derived from the already-resolved adopted service identity (SERVICE_HOME / KIMAKI_DATA_DIR), never a hardcoded path. Channels without env render byte-identically to the historical four-key form. Vendor-specific paths stay in installer config, not the transport. 2. Transport (defense-in-depth, layer-pure): build_env_map() no longer lets a poisoned HOME pass through. If HOME is not channel-pinned and the effective value is empty or points at a non-writable directory, it is dropped so proc_open uses a system default instead of a guaranteed-unwritable path. No vendor names or hardcoded paths in the generic transport. Adds regression coverage: unwritable inherited HOME is dropped, other parent env still inherited, and a channel-pinned writable HOME wins over a poisoned inherited one. Closes #228
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The scheduled
EC Agent Progress PingData Machine flow (flow 4) failed on dispatch with:…while the same flow run manually as the
opencodeuser succeeded. The pipeline is fine — the only difference is the HOME of the process that shells out to kimaki.Root cause — the unit-vs-dispatch asymmetry
There are two paths that spawn kimaki, and only one of them set HOME:
kimaki.service)Environment=HOME=$SERVICE_HOME+Environment=KIMAKI_DATA_DIR=...(fixed for the unit in #233/commit aaace07)agents/dispatch-message→ CLI transport)bridge:kimakichannel block emittedcommand/args/detach/timeoutbut noenv, so the generic transport'sbuild_env_map()returnednullandproc_openinherited the caller's envUnder the WP-cron heartbeat (
wp cron event runas www-data, whose passwd HOME is/var/www— root-owned,0755, unwritable), kimaki can't create$HOME/.kimakiand exits 64. The interactiveopencodeshell has a writable HOME, which is why manual runs worked. This is the same HOME-reset / config-dir-not-writable class as #93/#198.The fix — both halves (durable across upgrades)
1. Installer (config) — the primary, upgrade-surviving fix
lib/cli-channel.sh:cli_channel_register/_cli_channel_render_blockgain an optionalenv_jsonarg that renders an'env' => [ ... ]line into the channel block. A new_cli_channel_json_object_to_php_arrayhelper (python3 + naive fallback, mirroring the existing array converter) converts the JSON object. Channels with no env render byte-identically to the historical four-key block — no churn for cc-connect/telegram.bridges/kimaki.sh→_kimaki_register_cli_channel: stampsHOME+KIMAKI_DATA_DIRinto the channelenv, derived from the already-resolved adopted service identity (SERVICE_HOME/KIMAKI_DATA_DIR) — never a hardcoded/home/opencode, so a RUN_AS_ROOT install pins/rootand a non-root install pins the service user's home. Mirrors exactly what_kimaki_install_systemddoes for the unit.Generated block now looks like:
The transport already overlays
$config['env']on top of the inherited env (build_env_map), so this configured HOME wins →proc_openno longer inherits/var/www.2. Transport (defense-in-depth, layer-pure)
templates/wp-coding-agents-cli-transport.php→build_env_map()no longer blindly passes a poisoned HOME through. If HOME is not channel-pinned and the effective value is empty or points at a non-writable directory, HOME is dropped soproc_openfalls back to the system/account default instead of a guaranteed-unwritable path.Verification
php tests/smoke-cli-transport.php— 27/27 pass, including 3 new regression assertions:tests/cli-channel-perms.sh,tests/cli-channel-binary-path.sh,tests/cli-transport-install.sh— all pass (binary-path exercises_kimaki_register_cli_channelwith no SERVICE_HOME → emits no env → backward-compatible).php -lclean on the transport + smoke test;bash -nclean onlib/cli-channel.sh+bridges/kimaki.sh.Closes #228
cc <@1493317298151489577>