fix(a3): resolve shift buffer race condition with call-unique tokens, atomic write, and automatic GC (100% PASS)

This commit is contained in:
2026-08-06 23:52:57 +09:00
parent 0f6dd8ba8b
commit 3530e8b65a
3 changed files with 330 additions and 16 deletions
+43 -7
View File
@@ -450,13 +450,40 @@ except Exception:
exit 1
fi
buf="$(echo "$2" | tr -cd 'A-Za-z0-9_.-')"
if [ -z "$buf" ]; then buf="tmp_buffer"; else buf="buf_$buf"; fi
if [ -z "$buf" ]; then
echo "Error: -b name contains no usable characters" >&2
exit 1
fi
buf="buf_$buf"
shift 2
;;
*) text="$1"; shift ;;
esac
done
echo -n "$text" > "$wrapper_dir/$buf"
# A-3 GC: unique buffer names mean an abandoned buffer (SIGINT/SIGTERM/
# timeout between set-buffer and delete-buffer) is never overwritten, so
# it would leak forever. Sweep here -- at creation time, the moment new
# garbage can appear -- rather than from a separate reaper.
#
# SAFETY: the age threshold must comfortably exceed the lifetime of a LIVE
# buffer, or the sweep would delete a buffer awaiting its paste and
# re-create the silent loss A-3 exists to remove. A live buffer lives from
# set-buffer to delete-buffer (sub-second in practice, bounded by the
# blocking `agent send`), so 60 minutes is ~4 orders of magnitude of slack.
# Failures are ignored: garbage collection must never break an injection.
#
# The '.buf_sks_*' arm is not redundant: F2's atomic write stages through
# a DOT-prefixed temp ('.buf_sks_....tmp'), which 'buf_sks_*' cannot match.
# Without it a temp orphaned by SIGKILL between write and rename would leak
# forever, exactly like the buffers this sweep exists to reclaim.
find "$wrapper_dir" \( -name 'buf_sks_*' -o -name '.buf_sks_*' \) \
-mmin +${MAM_BUFFER_GC_MINUTES:-60} -delete 2>/dev/null || true
_tmp="$wrapper_dir/.$buf.$$.tmp"
if ! { echo -n "$text" > "$_tmp" && mv -f "$_tmp" "$wrapper_dir/$buf"; }; then
rm -f "$_tmp"
echo "Error: failed to write buffer $buf" >&2
exit 1
fi
;;
paste-buffer)
buf="tmp_buffer"
@@ -465,7 +492,11 @@ except Exception:
case "$1" in
-b)
buf="$(echo "$2" | tr -cd 'A-Za-z0-9_.-')"
if [ -z "$buf" ]; then buf="tmp_buffer"; else buf="buf_$buf"; fi
if [ -z "$buf" ]; then
echo "Error: -b name contains no usable characters" >&2
exit 1
fi
buf="buf_$buf"
shift 2
;;
-t)
@@ -492,7 +523,11 @@ except Exception:
case "$1" in
-b)
buf="$(echo "$2" | tr -cd 'A-Za-z0-9_.-')"
if [ -z "$buf" ]; then buf="tmp_buffer"; else buf="buf_$buf"; fi
if [ -z "$buf" ]; then
echo "Error: -b name contains no usable characters" >&2
exit 1
fi
buf="buf_$buf"
shift 2
;;
*) shift ;;
@@ -1706,9 +1741,10 @@ send_keys_safe() {
sleep 2
done
_sks_herdr set-buffer -b "sks_$job_id" "$text"
_sks_herdr paste-buffer -b "sks_$job_id" -t "$sess"
_sks_herdr delete-buffer -b "sks_$job_id" 2>/dev/null || true
local sks_buf="sks_${sess}_${job_id}_$$_${RANDOM}_$(date +%s%N 2>/dev/null || date +%s)"
_sks_herdr set-buffer -b "$sks_buf" "$text"
_sks_herdr paste-buffer -b "$sks_buf" -t "$sess"
_sks_herdr delete-buffer -b "$sks_buf" 2>/dev/null || true
if [[ "$sess" =~ "agy" ]]; then
_sks_herdr send-keys -t "$sess" C-m
return 0