Skip to content
Merged
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
9 changes: 9 additions & 0 deletions lib/cli-channel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ cli_channel_ensure_mu_plugin_file() {

mkdir -p "$dir"

# Write the scaffold, then force mode 0644 regardless of the caller's
# umask (root cron/systemd contexts default to 0077 which strips the
# world-read bit PHP-FPM needs — see issue #133).
cat > "$file" <<'PHP'
<?php
/**
Expand Down Expand Up @@ -143,6 +146,8 @@ add_filter( 'datamachine_code_cli_channels', function ( $channels ) {
} );
PHP

chmod 0644 "$file"

log " Wrote CLI-channel mu-plugin scaffold: $file"
if [ -n "${UPDATED_ITEMS+x}" ]; then
UPDATED_ITEMS+=("created $file")
Expand Down Expand Up @@ -280,6 +285,9 @@ cli_channel_register() {
fi

mv "$tmp" "$file"
# Self-heal legacy 0600 files written before the umask fix in #133.
# mktemp creates with mode 0600 so mv preserves that — force 0644.
chmod 0644 "$file"
log " Registered CLI channel '$name' in $file"
if [ -n "${UPDATED_ITEMS+x}" ]; then
UPDATED_ITEMS+=("CLI channel: $name")
Expand Down Expand Up @@ -316,6 +324,7 @@ cli_channel_unregister() {
tmp=$(mktemp "${file}.XXXXXX")
_cli_channel_rewrite "$file" "$name" "" > "$tmp"
mv "$tmp" "$file"
chmod 0644 "$file"
log " Unregistered CLI channel '$name' from $file"
if [ -n "${UPDATED_ITEMS+x}" ]; then
UPDATED_ITEMS+=("CLI channel removed: $name")
Expand Down
9 changes: 9 additions & 0 deletions lib/runtime-signature.sh
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ runtime_signature_ensure_mu_plugin_file() {

mkdir -p "$dir"

# Write the scaffold, then force mode 0644 regardless of the caller's
# umask (root cron/systemd contexts default to 0077 which strips the
# world-read bit PHP-FPM needs — see issue #133).
cat > "$file" <<'PHP'
<?php
/**
Expand Down Expand Up @@ -154,6 +157,8 @@ add_filter( 'datamachine_code_worktree_runtime_signatures', function ( $signatur
} );
PHP

chmod 0644 "$file"

log " Wrote runtime-signature mu-plugin scaffold: $file"
if [ -n "${UPDATED_ITEMS+x}" ]; then
UPDATED_ITEMS+=("created $file")
Expand Down Expand Up @@ -273,6 +278,9 @@ runtime_signature_register() {
fi

mv "$tmp" "$file"
# Self-heal legacy 0600 files written before the umask fix in #133.
# mktemp creates with mode 0600 so mv preserves that — force 0644.
chmod 0644 "$file"
log " Registered runtime signature '$runtime_id' in $file"
if [ -n "${UPDATED_ITEMS+x}" ]; then
UPDATED_ITEMS+=("runtime signature: $runtime_id")
Expand Down Expand Up @@ -310,6 +318,7 @@ runtime_signature_unregister() {
tmp=$(mktemp "${file}.XXXXXX")
_runtime_signature_rewrite "$file" "$runtime_id" "" > "$tmp"
mv "$tmp" "$file"
chmod 0644 "$file"
log " Unregistered runtime signature '$runtime_id' from $file"
if [ -n "${UPDATED_ITEMS+x}" ]; then
UPDATED_ITEMS+=("runtime signature removed: $runtime_id")
Expand Down
101 changes: 101 additions & 0 deletions tests/cli-channel-perms.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/bin/bash
# tests/cli-channel-perms.sh — Regression coverage for issue #133 in
# lib/cli-channel.sh.
#
# The mu-plugin file written by cli_channel_ensure_mu_plugin_file /
# cli_channel_register must land at mode 0644 regardless of the caller's
# umask, because PHP-FPM (running as www-data) needs world-read to load it.
# Root cron/systemd contexts default to umask 0077 → 0600, which broke
# every install that ran setup.sh / upgrade.sh from a daemon context.
#
# Asserts the three write paths each produce mode 0644:
# 1. Fresh scaffold (cli_channel_ensure_mu_plugin_file)
# 2. Subsequent register replacing/inserting a block (mktemp + mv)
# 3. Unregister removing a block (mktemp + mv)
#
# Also asserts the self-heal behavior: if a legacy file is mode 0600
# (left by a pre-#133 install), the next register call must restore 0644.
set -eu

SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$SCRIPT_DIR"

TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

mkdir -p "$TMP/wp-content/mu-plugins"
export SITE_PATH="$TMP"
export DRY_RUN=false

# shellcheck disable=SC1091
source "$SCRIPT_DIR/lib/common.sh"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/lib/cli-channel.sh"
UPDATED_ITEMS=()

# Silence helper logs unless --verbose.
VERBOSE=false
for arg in "$@"; do
case "$arg" in
--verbose|-v) VERBOSE=true ;;
esac
done
if [ "$VERBOSE" = false ]; then
log() { :; }
warn() { echo "WARN: $1" >&2; }
fi

MU_FILE="$TMP/wp-content/mu-plugins/wp-coding-agents-channels.php"
FAILED=0

assert_mode_0644() {
local file="$1" name="$2"
local got
got=$(stat -c %a "$file")
if [ "$got" = "644" ]; then
echo " ok $name"
else
echo " FAIL $name"
echo " got: $got"
echo " want: 644"
FAILED=$((FAILED + 1))
fi
}

# --- 1. Fresh scaffold under hostile umask ---------------------------------
echo "==> register kimaki (fresh scaffold, umask 077)"
(
umask 077
cli_channel_register "kimaki" \
"/usr/local/bin/kimaki" \
'["send","--channel","{recipient}","--prompt","{message}"]'
)
assert_mode_0644 "$MU_FILE" "fresh scaffold lands as 0644 under umask 077"

# --- 2. Sibling register exercises mktemp + mv path; self-heal from 0600 ---
echo "==> simulate legacy 0600 file and verify self-heal on next register"
chmod 0600 "$MU_FILE"
(
umask 077
cli_channel_register "opencode-telegram" \
"/usr/local/bin/opencode-telegram" \
'["dispatch","--chat","{recipient}","--text","{message}"]'
)
assert_mode_0644 "$MU_FILE" "mu-plugin self-healed to 0644 after sibling register"

# --- 3. Unregister also lands as 0644 --------------------------------------
echo "==> unregister opencode-telegram"
chmod 0600 "$MU_FILE"
(
umask 077
cli_channel_unregister "opencode-telegram"
)
assert_mode_0644 "$MU_FILE" "mu-plugin mode 0644 after unregister"

# --- Done ------------------------------------------------------------------
echo
if [ "$FAILED" -gt 0 ]; then
echo "FAILED: $FAILED assertion(s)"
exit 1
fi
echo "OK: all cli-channel perms assertions passed"
8 changes: 8 additions & 0 deletions tests/effective-prompt/__snapshots__/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Diff-intermediate files written by tests/effective-prompt/run.mjs when
# a snapshot mismatch occurs. They mirror the canonical .baseline.txt /
# .filtered.txt / .raw.txt and are only useful for local inspection.
#
# The harness already cleans these up on a successful run; this file
# keeps them out of git when a run fails and they are left in place
# for debugging. See issue #134.
*.actual
13 changes: 12 additions & 1 deletion tests/effective-prompt/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
//
// Exit code: 0 on success, 1 on any assertion failure.

import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync } from "node:fs"
import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync, unlinkSync } from "node:fs"
import { execSync } from "node:child_process"
import { dirname, join } from "node:path"
import { fileURLToPath, pathToFileURL } from "node:url"
Expand Down Expand Up @@ -325,6 +325,17 @@ for (const r of results) {
if (!r.skipped && r.failures.length > 0) failed++
}

// Clean up .actual diff intermediates on a fully successful run so they do
// not litter the working tree (issue #134). On any failure, leave them in
// place so the reviewer can diff against the canonical snapshot.
if (failed === 0) {
for (const f of readdirSync(SNAPSHOT_DIR)) {
if (f.endsWith(".actual")) {
try { unlinkSync(join(SNAPSHOT_DIR, f)) } catch {}
}
}
}

console.log(`\n${"=".repeat(72)}`)
if (failed === 0) {
console.log(`OK — ${results.filter((r) => !r.skipped).length} scenario(s) passed`)
Expand Down
33 changes: 29 additions & 4 deletions tests/runtime-signature.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,32 @@ assert_php_lint() {
fi
}

assert_mode_0644() {
local file="$1" name="$2"
local got
got=$(stat -c %a "$file")
if [ "$got" = "644" ]; then
echo " ok $name"
else
echo " FAIL $name"
echo " got: $got"
echo " want: 644"
FAILED=$((FAILED + 1))
fi
}

# --- 1. Fresh scaffold + register kimaki -----------------------------------
echo "==> register kimaki (fresh scaffold)"
runtime_signature_register "kimaki" \
'{"session_id":"KIMAKI_SESSION_ID","thread_id":"KIMAKI_THREAD_ID","thread_url":"KIMAKI_THREAD_URL"}'
# Use a hostile umask (matches root cron/systemd default 0077) to prove the
# helper forces 0644 regardless of caller umask — see issue #133.
echo "==> register kimaki (fresh scaffold, umask 077)"
(
umask 077
runtime_signature_register "kimaki" \
'{"session_id":"KIMAKI_SESSION_ID","thread_id":"KIMAKI_THREAD_ID","thread_url":"KIMAKI_THREAD_URL"}'
)
assert_file_exists "$MU_FILE" "mu-plugin created"
assert_php_lint "$MU_FILE" "scaffold parses with php -l"
assert_mode_0644 "$MU_FILE" "mu-plugin mode 0644 after fresh write under umask 077"

if grep -q "BEGIN runtime:kimaki" "$MU_FILE"; then
echo " ok kimaki block present"
Expand All @@ -106,10 +126,14 @@ HASH_AFTER=$(md5sum "$MU_FILE" | cut -d' ' -f1)
assert_eq "$HASH_AFTER" "$HASH_BEFORE" "file unchanged on re-register"

# --- 3. Add opencode without disturbing kimaki -----------------------------
echo "==> register opencode (sibling block)"
# Simulate a legacy 0600 file from a pre-#133 install. The next register call
# must self-heal it back to 0644 via the mktemp+mv path.
echo "==> simulate legacy 0600 file and verify self-heal on next register"
chmod 0600 "$MU_FILE"
runtime_signature_register "opencode" \
'{"session_id":"OPENCODE_SESSION_ID","run_id":"OPENCODE_RUN_ID"}'
assert_php_lint "$MU_FILE" "two-runtime file parses with php -l"
assert_mode_0644 "$MU_FILE" "mu-plugin mode self-healed to 0644 after sibling register"

if grep -q "BEGIN runtime:kimaki" "$MU_FILE" && grep -q "BEGIN runtime:opencode" "$MU_FILE"; then
echo " ok both runtime blocks present"
Expand Down Expand Up @@ -151,6 +175,7 @@ else
FAILED=$((FAILED + 1))
fi
assert_php_lint "$MU_FILE" "post-unregister file parses with php -l"
assert_mode_0644 "$MU_FILE" "mu-plugin mode 0644 after unregister"

# --- 6. Filter-shape end-to-end (php execution) ----------------------------
echo "==> apply_filters returns the expected shape"
Expand Down
Loading