claude-multiacc 2.0.25 → 2.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/claude-accounts +28 -8
- package/install.sh +4 -0
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/package.json +1 -1
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/run-tests.sh +18 -0
package/bin/claude-accounts
CHANGED
|
@@ -1434,7 +1434,21 @@ limits_distribute() {
|
|
|
1434
1434
|
sync_is_replica && return 0
|
|
1435
1435
|
command -v rsync >/dev/null 2>&1 || return 0
|
|
1436
1436
|
[ "${CLAUDE_MULTIACC_NO_DISTRIBUTE:-0}" = "1" ] && return 0
|
|
1437
|
-
|
|
1437
|
+
# In its OWN session, not merely backgrounded. launchd tears down the job's
|
|
1438
|
+
# whole process group the moment `limits` exits, and a `( … & )` subshell is
|
|
1439
|
+
# still in that group: every scheduled pass spawned its push and launchd killed
|
|
1440
|
+
# it before one rsync had finished, with nothing logged — while this machine
|
|
1441
|
+
# refreshed every 15 minutes, the seven runner Macs ranked on readings last
|
|
1442
|
+
# pushed at 21:13Z until 01:10Z on 2026-09-09 (a foreground pass delivered them
|
|
1443
|
+
# in seconds). setsid puts the push outside the group; install.sh also marks
|
|
1444
|
+
# the agent AbandonProcessGroup, and either one alone is enough.
|
|
1445
|
+
"$PYBIN" -c '
|
|
1446
|
+
import os, sys
|
|
1447
|
+
if os.fork():
|
|
1448
|
+
sys.exit(0)
|
|
1449
|
+
os.setsid()
|
|
1450
|
+
os.execv(sys.argv[1], [sys.argv[1], "limits-distribute-now"])
|
|
1451
|
+
' "$BIN_DIR/claude-accounts" >/dev/null 2>&1 </dev/null &
|
|
1438
1452
|
return 0
|
|
1439
1453
|
}
|
|
1440
1454
|
|
|
@@ -1472,21 +1486,26 @@ limits_distribute_now() {
|
|
|
1472
1486
|
done
|
|
1473
1487
|
if [ ! -s "$list" ]; then rm -f "$list"; return 0; fi
|
|
1474
1488
|
|
|
1489
|
+
local pushed=0 failed=0
|
|
1475
1490
|
server="$(sync_target)"
|
|
1476
1491
|
sroot="$(sync_target_root)"
|
|
1477
1492
|
if [ -n "$server" ] && ! sync_target_is_local "$server" \
|
|
1478
1493
|
&& valid_ssh_target "$server" && valid_remote_path "$sroot"; then
|
|
1479
|
-
limits_push_to "$server" "$sroot" "$list"
|
|
1494
|
+
if limits_push_to "$server" "$sroot" "$list"; then pushed=$((pushed + 1)); else failed=$((failed + 1)); fi
|
|
1480
1495
|
fi
|
|
1481
1496
|
# Peers are the OTHER Macs — the ones actually running tasks, and so the ones
|
|
1482
|
-
# whose selection goes blind without this.
|
|
1483
|
-
|
|
1497
|
+
# whose selection goes blind without this. Read through process substitution,
|
|
1498
|
+
# not a pipe: the counters below must survive the loop.
|
|
1499
|
+
while IFS="$(printf '\t')" read -r pt pr pp; do
|
|
1484
1500
|
[ "$pt" = "MALFORMED" ] && continue
|
|
1485
1501
|
[ -n "$pt" ] && [ -n "$pr" ] || continue
|
|
1486
1502
|
valid_ssh_target "$pt" || continue
|
|
1487
1503
|
valid_remote_path "$pr" || continue
|
|
1488
|
-
limits_push_to "$pt" "$pr" "$list"
|
|
1489
|
-
done
|
|
1504
|
+
if limits_push_to "$pt" "$pr" "$list"; then pushed=$((pushed + 1)); else failed=$((failed + 1)); fi
|
|
1505
|
+
done < <(manifest_peers)
|
|
1506
|
+
# Every pass writes its outcome: a silent push is indistinguishable from a
|
|
1507
|
+
# killed one, which is exactly how the 2026-09-08 outage went unnoticed.
|
|
1508
|
+
log_to sync.log "limits distributed to $pushed target(s), $failed failed"
|
|
1490
1509
|
rm -f "$list"
|
|
1491
1510
|
rmdir "$lock" 2>/dev/null || true
|
|
1492
1511
|
trap - EXIT
|
|
@@ -1497,8 +1516,7 @@ limits_push_to() { # $1 target, $2 remote root, $3 file list
|
|
|
1497
1516
|
rsync -az --timeout=20 \
|
|
1498
1517
|
-e 'ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new' \
|
|
1499
1518
|
--files-from="$3" "$ACC_ROOT/" "$1:$2/" >>"$ACC_ROOT/sync.log" 2>&1 \
|
|
1500
|
-
|| log_to sync.log "limits push to $1 failed (telemetry there will age)"
|
|
1501
|
-
return 0
|
|
1519
|
+
|| { log_to sync.log "limits push to $1 failed (telemetry there will age)"; return 1; }
|
|
1502
1520
|
}
|
|
1503
1521
|
|
|
1504
1522
|
cmd_limits() {
|
|
@@ -2956,6 +2974,8 @@ case "${1:-help}" in
|
|
|
2956
2974
|
sync) shift; cmd_sync "$@" ;;
|
|
2957
2975
|
verify) shift; cmd_verify "$@" ;;
|
|
2958
2976
|
limits) shift; cmd_limits "$@" ;;
|
|
2977
|
+
# Internal: the detached telemetry push `limits` starts in its own session.
|
|
2978
|
+
limits-distribute-now) shift; limits_distribute_now ;;
|
|
2959
2979
|
post-sync) shift; cmd_post_sync "$@" ;;
|
|
2960
2980
|
health) shift; cmd_health "$@" ;;
|
|
2961
2981
|
self-update) shift; cmd_self_update "$@" ;;
|
package/install.sh
CHANGED
|
@@ -183,6 +183,9 @@ mac_schedule_install() {
|
|
|
183
183
|
</array>
|
|
184
184
|
$(plist_env_block) <key>StartInterval</key><integer>900</integer>
|
|
185
185
|
<key>RunAtLoad</key><true/>
|
|
186
|
+
<!-- The pass hands its readings to the peers from a detached push; launchd
|
|
187
|
+
must not kill that push with the job's process group. -->
|
|
188
|
+
<key>AbandonProcessGroup</key><true/>
|
|
186
189
|
<key>StandardOutPath</key><string>/dev/null</string>
|
|
187
190
|
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
188
191
|
</dict></plist>
|
|
@@ -238,6 +241,7 @@ EOF
|
|
|
238
241
|
</array>
|
|
239
242
|
$(plist_env_block) <key>StartInterval</key><integer>300</integer>
|
|
240
243
|
<key>RunAtLoad</key><true/>
|
|
244
|
+
<key>AbandonProcessGroup</key><true/>
|
|
241
245
|
<key>StandardOutPath</key><string>/dev/null</string>
|
|
242
246
|
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
243
247
|
</dict></plist>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/tests/run-tests.sh
CHANGED
|
@@ -3759,6 +3759,7 @@ mkdir -p "$RSFAKE"
|
|
|
3759
3759
|
cat > "$RSFAKE/rsync" <<'EOF'
|
|
3760
3760
|
#!/usr/bin/env bash
|
|
3761
3761
|
printf 'RSYNC %s\n' "$*" >> "${RSLOG:?}"
|
|
3762
|
+
printf 'PGID %s\n' "$(ps -o pgid= -p $$ | tr -d ' ')" >> "$RSLOG"
|
|
3762
3763
|
for a in "$@"; do
|
|
3763
3764
|
case "$a" in
|
|
3764
3765
|
--files-from=*)
|
|
@@ -3795,6 +3796,23 @@ for peer in gas@mini-3 gas@mini-4; do
|
|
|
3795
3796
|
&& t_ok "telemetry is pushed to manifest peer $peer" \
|
|
3796
3797
|
|| t_fail "limits distribute peer" "$peer never received a push: $(grep '^RSYNC' "$RSLOG")"
|
|
3797
3798
|
done
|
|
3799
|
+
# The push must run OUTSIDE the calling job's process group: launchd kills that group
|
|
3800
|
+
# the moment `limits` exits, and a `( … & )` subshell is still in it — on 2026-09-08 the
|
|
3801
|
+
# source Mac refreshed every 15 minutes while every scheduled push died before its first
|
|
3802
|
+
# rsync, and the seven runner Macs ranked on 21:13Z readings until 01:10Z the next day.
|
|
3803
|
+
own_pgid="$(ps -o pgid= -p $$ | tr -d ' ')"
|
|
3804
|
+
push_pgids="$(grep '^PGID ' "$RSLOG" | sort -u | sed 's/^PGID //' | tr '\n' ' ')"
|
|
3805
|
+
case " $push_pgids " in
|
|
3806
|
+
*" $own_pgid "*) t_fail "detached push session" "a push ran inside the caller's process group $own_pgid (launchd would kill it)" ;;
|
|
3807
|
+
*) [ -n "$push_pgids" ] && t_ok "the telemetry push runs in its own session, outside the job's process group" \
|
|
3808
|
+
|| t_fail "detached push session" "no push recorded a process group" ;;
|
|
3809
|
+
esac
|
|
3810
|
+
grep -q "limits distributed to 3 target(s), 0 failed" "$DPOOL/sync.log" \
|
|
3811
|
+
&& t_ok "every distribute pass records its outcome in sync.log" \
|
|
3812
|
+
|| t_fail "distribute summary" "sync.log: $(tail -3 "$DPOOL/sync.log" 2>/dev/null | tr '\n' '|')"
|
|
3813
|
+
[ "$(grep -c '<key>AbandonProcessGroup</key><true/>' "$REPO_DIR/install.sh")" = "2" ] \
|
|
3814
|
+
&& t_ok "install.sh abandons the process group of both limits agents" \
|
|
3815
|
+
|| t_fail "AbandonProcessGroup" "expected both limits plists to carry AbandonProcessGroup"
|
|
3798
3816
|
# ...and every target gets the SAME list: each account's reading, plus any marker.
|
|
3799
3817
|
for f in "acct-01/limits.json" "acct-02/limits.json" "acct-02/.limited"; do
|
|
3800
3818
|
[ "$(grep -c "^FILE $f\$" "$RSLOG")" = "3" ] \
|