fix: feat: stack lock protocol for singleton project stack access (#255)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful

Fix python3 -c injection: pass lock_file as sys.argv[1] instead of
interpolating it inside the double-quoted -c string. Removes the
single-quote escape risk when project names contain special chars.
Also drop the misleading "atomic" comment on the tmp+mv write.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-04-06 07:09:26 +00:00
parent a5d3f238bf
commit bf2842eff8

View file

@ -65,8 +65,8 @@ stack_lock_check() {
fi
local holder heartbeat
holder=$(python3 -c "import sys,json; d=json.load(open('$lock_file')); print(d.get('holder','unknown'))" 2>/dev/null || echo "unknown")
heartbeat=$(python3 -c "import sys,json; d=json.load(open('$lock_file')); print(d.get('heartbeat',''))" 2>/dev/null || echo "")
holder=$(python3 -c 'import sys,json; d=json.load(open(sys.argv[1])); print(d.get("holder","unknown"))' "$lock_file" 2>/dev/null || echo "unknown")
heartbeat=$(python3 -c 'import sys,json; d=json.load(open(sys.argv[1])); print(d.get("heartbeat",""))' "$lock_file" 2>/dev/null || echo "")
if [ -z "$heartbeat" ]; then
echo "stale:${holder}"
@ -107,7 +107,7 @@ stack_lock_acquire() {
case "$status" in
free)
# Attempt atomic write using a temp file + mv
# Write to temp file then rename to avoid partial reads by other processes
local tmp_lock
tmp_lock=$(mktemp "${STACK_LOCK_DIR}/.lock-tmp-XXXXXX")
local now
@ -156,11 +156,11 @@ stack_lock_heartbeat() {
[ -f "$lock_file" ] || return 0
local current_holder
current_holder=$(python3 -c "import sys,json; d=json.load(open('$lock_file')); print(d.get('holder',''))" 2>/dev/null || echo "")
current_holder=$(python3 -c 'import sys,json; d=json.load(open(sys.argv[1])); print(d.get("holder",""))' "$lock_file" 2>/dev/null || echo "")
[ "$current_holder" = "$holder" ] || return 0
local since
since=$(python3 -c "import sys,json; d=json.load(open('$lock_file')); print(d.get('since',''))" 2>/dev/null || echo "")
since=$(python3 -c 'import sys,json; d=json.load(open(sys.argv[1])); print(d.get("since",""))' "$lock_file" 2>/dev/null || echo "")
local now
now=$(_stack_lock_now)
@ -185,7 +185,7 @@ stack_lock_release() {
if [ -n "$holder" ]; then
local current_holder
current_holder=$(python3 -c "import sys,json; d=json.load(open('$lock_file')); print(d.get('holder',''))" 2>/dev/null || echo "")
current_holder=$(python3 -c 'import sys,json; d=json.load(open(sys.argv[1])); print(d.get("holder",""))' "$lock_file" 2>/dev/null || echo "")
if [ "$current_holder" != "$holder" ]; then
echo "[stack-lock] refusing to release: lock held by '${current_holder}', not '${holder}'" >&2
return 1