t3-provision-users: install_skills heals stale symlinks + owns ~/.agents
All checks were successful
ci/woodpecker/push/default Pipeline was successful
All checks were successful
ci/woodpecker/push/default Pipeline was successful
Follow-up to the vendored-skills change, from verifying the emo rollout: - The if-absent guard treated ANY pre-existing ~/.claude/skills/<name> entry as "installed", so a manual cross-user symlink emo already had (grill-me -> /home/wizard/.claude/skills/grill-me) was skipped — leaving the requested skill depending on the admin's home instead of emo's own copy. The guard now keys on the user's OWN copy (a real dir under ~/.agents/skills) and (re)points the ~/.claude/skills symlink at it, healing a stale/cross-user link while still never clobbering a real dir. - install -d left the intermediate ~/.agents owned by root; now owned by the user. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
987fdd16db
commit
1c8dc6bd6c
1 changed files with 21 additions and 12 deletions
|
|
@ -438,8 +438,9 @@ install_memory() {
|
||||||
# `npx skills` upstream drifted off this exact set, so we reproduce it offline + deterministically.
|
# `npx skills` upstream drifted off this exact set, so we reproduce it offline + deterministically.
|
||||||
# if-absent + ADDITIVE: copies a skill dir into ~/.agents/skills/<name> (owned by the user) and
|
# if-absent + ADDITIVE: copies a skill dir into ~/.agents/skills/<name> (owned by the user) and
|
||||||
# symlinks ~/.claude/skills/<name> -> ../../.agents/skills/<name> (the layout `skills add -g`
|
# symlinks ~/.claude/skills/<name> -> ../../.agents/skills/<name> (the layout `skills add -g`
|
||||||
# produces; Claude Code reads ~/.claude/skills/). Scoped to SKILL_USERS; never clobbers an existing
|
# produces; Claude Code reads ~/.claude/skills/). Scoped to SKILL_USERS. if-absent keys on the
|
||||||
# skill. Best-effort tail: must return 0 or set -euo pipefail aborts the whole reconcile.
|
# user's OWN copy, so it heals a stale/cross-user ~/.claude/skills symlink but never clobbers a real
|
||||||
|
# skill dir. Best-effort tail: must return 0 or set -euo pipefail aborts the whole reconcile.
|
||||||
install_skills() {
|
install_skills() {
|
||||||
local user="$1" home
|
local user="$1" home
|
||||||
home="$(getent passwd "$user" | cut -d: -f6)"
|
home="$(getent passwd "$user" | cut -d: -f6)"
|
||||||
|
|
@ -456,24 +457,32 @@ install_skills() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local agents_dir="$home/.agents/skills" claude_dir="$home/.claude/skills"
|
local agents_dir="$home/.agents/skills" claude_dir="$home/.claude/skills"
|
||||||
install -d -o "$user" -g "$user" -m 0755 "$agents_dir" "$claude_dir"
|
# own the parent ~/.agents too (install -d leaves created intermediates root-owned)
|
||||||
|
install -d -o "$user" -g "$user" -m 0755 "$home/.agents" "$agents_dir" "$claude_dir"
|
||||||
|
chown "$user:$user" "$home/.agents" || true
|
||||||
|
|
||||||
local skill name dst n=0
|
local skill name dst link n=0
|
||||||
for skill in "$src_root"/*/; do
|
for skill in "$src_root"/*/; do
|
||||||
[[ -d "$skill" ]] || continue
|
[[ -d "$skill" ]] || continue
|
||||||
name="$(basename "$skill")"
|
name="$(basename "$skill")"
|
||||||
dst="$agents_dir/$name"
|
dst="$agents_dir/$name"
|
||||||
[[ -e "$dst" || -L "$claude_dir/$name" ]] && continue # if-absent: already installed
|
link="$claude_dir/$name"
|
||||||
if cp -a "$src_root/$name" "$dst"; then
|
# if-absent keys on the user's OWN copy (a real dir under ~/.agents/skills), NOT on any
|
||||||
chown -R "$user:$user" "$dst"
|
# pre-existing ~/.claude/skills entry — so a stale or cross-user symlink gets healed.
|
||||||
ln -sfn "../../.agents/skills/$name" "$claude_dir/$name"
|
if [[ ! -d "$dst" ]]; then
|
||||||
chown -h "$user:$user" "$claude_dir/$name"
|
cp -a "$src_root/$name" "$dst" || { log "WARN: copy skill $name -> $user failed"; continue; }
|
||||||
|
chown -R "$user:$user" "$dst" || true
|
||||||
n=$((n+1))
|
n=$((n+1))
|
||||||
else
|
fi
|
||||||
log "WARN: copy skill $name -> $user failed"
|
# point ~/.claude/skills/<name> at the user's own copy (replacing a stale/cross-user symlink);
|
||||||
|
# never clobber a real dir/file squatting that name.
|
||||||
|
if [[ -d "$link" && ! -L "$link" ]]; then
|
||||||
|
log "WARN: $claude_dir/$name is a real dir (left as-is) for $user"
|
||||||
|
elif [[ "$(readlink "$link" 2>/dev/null)" != "../../.agents/skills/$name" ]]; then
|
||||||
|
ln -sfn "../../.agents/skills/$name" "$link" && chown -h "$user:$user" "$link" || log "WARN: link skill $name -> $user failed"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [[ "$n" -gt 0 ]]; then log "vendored $n skill(s) -> $user"; fi
|
if [[ "$n" -gt 0 ]]; then log "vendored/healed $n skill(s) -> $user"; fi
|
||||||
return 0 # best-effort tail must never return non-zero, else set -euo pipefail aborts the reconcile
|
return 0 # best-effort tail must never return non-zero, else set -euo pipefail aborts the reconcile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue