From 5a7843591cab72796d8bd3462b32f569fba5ff35 Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 13:28:06 -0400 Subject: [PATCH 01/10] Don't ignore .java-version This is not a vendor specific concept, so we wanna allow the tracking of it. If I'm on a project that doesn't want this (but my tooling does) then I can override that ignore in that project via local means. --- git/gitignore.symlink | 1 - 1 file changed, 1 deletion(-) diff --git a/git/gitignore.symlink b/git/gitignore.symlink index 65f6f33..6821b2a 100644 --- a/git/gitignore.symlink +++ b/git/gitignore.symlink @@ -3,7 +3,6 @@ *.swp .sublime-workspace .sublime-project -.java-version .nvmrc .vscode .envrc From 752b520e28cf719df93f632ef5b5be058368ea4d Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 13:38:01 -0400 Subject: [PATCH 02/10] Move the up alias into bin/git-up The inline alias was one long line of escaped shell, effectively unreadable. Moving the body to bin/git-up lets it use real control flow, comments and usage docs, leaving a one-line alias behind. The alias points at the absolute path rather than relying on bin/ being on $PATH, so it also works from a minimal environment. Claude-Session: d019263c-0e35-4452-b1c2-f685d76d5c18 --- bin/git-up | 66 +++++++++++++++++++++++++++++++++++++++++++ git/gitconfig.symlink | 1 + 2 files changed, 67 insertions(+) create mode 100755 bin/git-up diff --git a/bin/git-up b/bin/git-up new file mode 100755 index 0000000..ad7a1c3 --- /dev/null +++ b/bin/git-up @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# +# Bring the current branch up to date with its base branch. +# +# Usage: git up [base] +# +# The base branch is, in order of preference: +# 1. the argument passed to this command +# 2. `branch..base` from git config +# 3. the remote's default branch (e.g. origin/HEAD) +# +# On the base branch itself this fast-forwards. On any other branch it +# rebases onto the base, autostashing local changes. + +set -e + +branch="$(git symbolic-ref --quiet --short HEAD)" || { + echo "up: not on a branch" + exit 1 +} + +remote="$(git config --get "branch.$branch.remote" || echo origin)" + +# Determine the base branch, falling back to the remote's default branch. +base="${1:-$(git config --get "branch.$branch.base" || true)}" + +if [ -z "$base" ]; then + git remote set-head "$remote" --auto > /dev/null 2>&1 || true + base="$(git symbolic-ref --quiet --short "refs/remotes/$remote/HEAD" | sed "s|^$remote/||")" +fi + +if [ -z "$base" ]; then + echo "up: could not determine a base branch" + exit 1 +fi + +# Already on the base branch: just fast-forward it. +if [ "$branch" = "$base" ]; then + git fetch "$remote" "$base" + git merge --ff-only "$remote/$base" + exit 0 +fi + +if git show-ref --verify --quiet "refs/heads/$base"; then + # Fast-forward the local base branch without checking it out. If that + # fails the local branch has diverged (or the remote has no such + # branch), so leave it alone and carry on with whatever we have. + if ! git fetch "$remote" "$base:$base" 2> /dev/null; then + if git fetch "$remote" "$base" 2> /dev/null; then + echo "up: local $base has diverged; leaving it alone" + else + echo "up: no $base on $remote; using local $base" + fi + fi +else + git fetch "$remote" "$base" +fi + +# Prefer the remote-tracking ref when one exists. +target="$base" +if git rev-parse --verify --quiet "refs/remotes/$remote/$base" > /dev/null; then + target="$remote/$base" +fi + +echo "up: rebasing $branch onto $target" +git rebase --autostash "$target" diff --git a/git/gitconfig.symlink b/git/gitconfig.symlink index 31052a2..923113d 100644 --- a/git/gitconfig.symlink +++ b/git/gitconfig.symlink @@ -36,6 +36,7 @@ sync = "!f() { remote=\"${GIT_UPSTREAM_NAME:-upstream}\"; branch=\"$(git rev-parse --abbrev-ref HEAD)\"; if [ \"$branch\" != \"main\" ] && [ \"$branch\" != \"master\" ]; then echo \"Error: Must be on main or master branch (currently on $branch)\"; return 1; fi; git fetch \"$remote\" --tags || return 1; if [ -n \"$1\" ]; then ref=\"$1\"; sync_branch=\"sync/${ref}\"; else git remote set-head \"$remote\" --auto 2>/dev/null; upstream_branch=\"$(git symbolic-ref \"refs/remotes/$remote/HEAD\" 2>/dev/null | sed \"s|refs/remotes/$remote/||\")\" || { echo \"Error: Could not determine default branch for $remote\"; return 1; }; ref=\"$remote/$upstream_branch\"; sync_branch=\"sync/$(date +%s)-${upstream_branch}\"; fi; git checkout -b \"$sync_branch\" && git merge \"$ref\" -m \"Merge upstream ${ref}\"; }; f" fpush = push --force-with-lease track = !git branch --set-upstream-to=origin/$(git symbolic-ref --short HEAD) + up = !~/.dotfiles/bin/git-up [pull] default = current rebase = true From 0e796bb84962ac5222193405c83708825f40291e Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 13:39:37 -0400 Subject: [PATCH 03/10] Move the cleanup alias into bin/git-cleanup Same treatment as git-up: the branch scan and the grep pattern it built by string substitution are far easier to follow as a real script. Behaviour is unchanged, including the fact that protected names match as substrings, so a branch like "devops" is spared too. Claude-Session: d019263c-0e35-4452-b1c2-f685d76d5c18 --- bin/git-cleanup | 36 ++++++++++++++++++++++++++++++++++++ git/gitconfig.symlink | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100755 bin/git-cleanup diff --git a/bin/git-cleanup b/bin/git-cleanup new file mode 100755 index 0000000..4dcee37 --- /dev/null +++ b/bin/git-cleanup @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# +# Delete local branches that have already been merged into the base branch. +# +# Usage: git cleanup [base] +# +# The base branch defaults to the first of these that exists locally: +# dev, development, main, master. + +set -e + +PROTECTED_BRANCHES=(dev development main master) + +base="$1" + +if [ -z "$base" ]; then + for candidate in "${PROTECTED_BRANCHES[@]}"; do + if git show-ref --verify --quiet "refs/heads/$candidate"; then + base="$candidate" + break + fi + done +fi + +# Never delete a protected branch, or the current one (marked "* name"). +# Note this is a substring match, so a branch named "devops" is also +# skipped -- it errs towards keeping branches rather than deleting them. +pattern="$( + IFS='|' + printf '%s' "${PROTECTED_BRANCHES[*]}" +)|\*" + +# With no base branch found, --merged falls back to HEAD. +git branch --merged ${base:+"$base"} \ + | grep -Ev "$pattern" \ + | xargs -n 1 git branch -d diff --git a/git/gitconfig.symlink b/git/gitconfig.symlink index 923113d..647cfe5 100644 --- a/git/gitconfig.symlink +++ b/git/gitconfig.symlink @@ -28,7 +28,7 @@ # NOTE: [user] section (name, email, signingkey) is configured in ~/.gitconfig.local # This file is generated from git/gitconfig.local.symlink.example during bootstrap [alias] - cleanup = "!f() { declare -a BASE_BRANCH_NAMES_DESC_PRIORTY=("dev" "development" "main" "master") && for i in "${BASE_BRANCH_NAMES_DESC_PRIORTY[@]}"; do if [ -z "$BASE_BRANCH" ]; then if $(git show-ref --verify --quiet refs/heads/$i); then BASE_BRANCH="$i"; fi; fi; done; BRANCH_NAMES_STRING="${BASE_BRANCH_NAMES_DESC_PRIORTY[@]}"; GREP_PARAMS="${BRANCH_NAMES_STRING// /\\\\|}\\\\\\|\\\\*"; git branch --merged ${1-$BASE_BRANCH} | grep -v "$GREP_PARAMS" | xargs -n 1 git branch -d; }; f" + cleanup = !~/.dotfiles/bin/git-cleanup brebase = "!f() { declare -a BASE_BRANCH_NAMES_DESC_PRIORTY=("dev" "development" "main" "master") && for i in "${BASE_BRANCH_NAMES_DESC_PRIORTY[@]}"; do if [ -z "$BASE_BRANCH" ]; then if $(git show-ref --verify --quiet refs/heads/$i); then BASE_BRANCH="$i"; fi; fi; done; git rebase -i `git merge-base HEAD ${1-$BASE_BRANCH}`; }; f" plog = log --oneline --graph --all --decorate --pretty='format:%C(yellow)%h%C(auto)%d %Creset%s %C(cyan)[%an] %C(blue)%ar' edit = !"f() { EDIT_STASH=0 && GIT_SEQUENCE_EDITOR=\"sed -i '' '1s/^pick/edit/'\" git rebase -i \"$1~\" && git reset HEAD~1 && git add -AN && git add -p && git commit -e -C HEAD@{1} && if [ -z \"$(git status --porcelain)\" ]; then git rebase --continue; else git add -A && EDIT_STASH=1 && git stash -q && git rebase --continue && git stash pop -q && EDIT_STASH=0 && git reset; fi || (echo 'Aborting the edit...' && if [ \"$EDIT_STASH\" = \"1\" ]; then git stash drop -q && EDIT_STASH=0; fi && git rebase --abort && git add -A && git reset); }; f" From 405771f228a5114e40281ac5ae81f5e485ad79e6 Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 13:40:09 -0400 Subject: [PATCH 04/10] Move the brebase alias into bin/git-brebase Carries the same base branch detection as cleanup, which was the bulk of the unreadable one-liner. The merge-base rebase it actually performs is one line once that is out of the way. Claude-Session: d019263c-0e35-4452-b1c2-f685d76d5c18 --- bin/git-brebase | 28 ++++++++++++++++++++++++++++ git/gitconfig.symlink | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100755 bin/git-brebase diff --git a/bin/git-brebase b/bin/git-brebase new file mode 100755 index 0000000..86082fb --- /dev/null +++ b/bin/git-brebase @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# +# Interactively rebase every commit this branch added on top of its base. +# +# Usage: git brebase [base] +# +# Rebasing from the merge-base means only this branch's own commits show +# up in the todo list, however far the base has moved on since. +# +# The base branch defaults to the first of these that exists locally: +# dev, development, main, master. + +set -e + +BASE_BRANCH_CANDIDATES=(dev development main master) + +base="$1" + +if [ -z "$base" ]; then + for candidate in "${BASE_BRANCH_CANDIDATES[@]}"; do + if git show-ref --verify --quiet "refs/heads/$candidate"; then + base="$candidate" + break + fi + done +fi + +git rebase -i "$(git merge-base HEAD ${base:+"$base"})" diff --git a/git/gitconfig.symlink b/git/gitconfig.symlink index 647cfe5..811e01b 100644 --- a/git/gitconfig.symlink +++ b/git/gitconfig.symlink @@ -29,7 +29,7 @@ # This file is generated from git/gitconfig.local.symlink.example during bootstrap [alias] cleanup = !~/.dotfiles/bin/git-cleanup - brebase = "!f() { declare -a BASE_BRANCH_NAMES_DESC_PRIORTY=("dev" "development" "main" "master") && for i in "${BASE_BRANCH_NAMES_DESC_PRIORTY[@]}"; do if [ -z "$BASE_BRANCH" ]; then if $(git show-ref --verify --quiet refs/heads/$i); then BASE_BRANCH="$i"; fi; fi; done; git rebase -i `git merge-base HEAD ${1-$BASE_BRANCH}`; }; f" + brebase = !~/.dotfiles/bin/git-brebase plog = log --oneline --graph --all --decorate --pretty='format:%C(yellow)%h%C(auto)%d %Creset%s %C(cyan)[%an] %C(blue)%ar' edit = !"f() { EDIT_STASH=0 && GIT_SEQUENCE_EDITOR=\"sed -i '' '1s/^pick/edit/'\" git rebase -i \"$1~\" && git reset HEAD~1 && git add -AN && git add -p && git commit -e -C HEAD@{1} && if [ -z \"$(git status --porcelain)\" ]; then git rebase --continue; else git add -A && EDIT_STASH=1 && git stash -q && git rebase --continue && git stash pop -q && EDIT_STASH=0 && git reset; fi || (echo 'Aborting the edit...' && if [ \"$EDIT_STASH\" = \"1\" ]; then git stash drop -q && EDIT_STASH=0; fi && git rebase --abort && git add -A && git reset); }; f" cremit = commit --reedit-message=HEAD From 38b3565f9f9494c211647f27645a31b57d0ef629 Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 13:42:29 -0400 Subject: [PATCH 05/10] Move the edit alias into bin/git-edit Easily the worst offender of the aliases, and the one whose error handling most needed to be readable. The script also records the caveats and links back to the gist it came from. Two behaviour changes fall out of the rewrite. Our copy had drifted from the gist and raised the stash flag before taking the stash, so an abort in between could drop an unrelated stash; the flag now goes up only once the stash exists. An abort also exits non-zero now, rather than returning the status of its own cleanup. Claude-Session: d019263c-0e35-4452-b1c2-f685d76d5c18 --- bin/git-edit | 62 +++++++++++++++++++++++++++++++++++++++++++ git/gitconfig.symlink | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100755 bin/git-edit diff --git a/bin/git-edit b/bin/git-edit new file mode 100755 index 0000000..a414c5a --- /dev/null +++ b/bin/git-edit @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# +# Amend or split a commit further back in history. +# +# Usage: git edit +# +# Rebases back to and uncommits it, then lets you pick the hunks +# that stay in it via `git add -p`. Anything you leave behind is carried +# forward so it lands on the following commit instead. +# +# Deliberately avoids `set -e`: every step routes failure to abort(), so a +# half-finished edit never leaves a rebase in progress. +# +# Caveats, per the source gist: +# - added files can be dropped from a commit, deleted files cannot +# - removing every change from a commit aborts +# - a conflict later in the rebase aborts +# - bailing out of `git add -p` needs a manual `git rebase --abort` +# +# Source: https://gist.github.com/slifty/68bd451896f6494b870280c00dc4fe9c + +edit_stash=0 + +abort() { + echo 'Aborting the edit...' + if [ "$edit_stash" = "1" ]; then + git stash drop -q + edit_stash=0 + fi + git rebase --abort + git add -A + git reset + exit 1 +} + +if [ -z "$1" ]; then + echo "edit: usage: git edit " + exit 1 +fi + +# Stop the rebase at the target commit by turning its "pick" into "edit". +GIT_SEQUENCE_EDITOR="sed -i '' '1s/^pick/edit/'" git rebase -i "$1~" || abort + +# Uncommit it, keeping the changes in the worktree, then choose what +# stays. The intent-to-add pass is what makes new files visible to -p. +git reset HEAD~1 || abort +git add -AN || abort +git add -p || abort +git commit -e -C HEAD@{1} || abort + +if [ -z "$(git status --porcelain)" ]; then + git rebase --continue || abort +else + # Hunks left behind ride along to the next commit. + git add -A || abort + git stash -q || abort + edit_stash=1 + git rebase --continue || abort + git stash pop -q || abort + edit_stash=0 + git reset || abort +fi diff --git a/git/gitconfig.symlink b/git/gitconfig.symlink index 811e01b..927cf17 100644 --- a/git/gitconfig.symlink +++ b/git/gitconfig.symlink @@ -31,7 +31,7 @@ cleanup = !~/.dotfiles/bin/git-cleanup brebase = !~/.dotfiles/bin/git-brebase plog = log --oneline --graph --all --decorate --pretty='format:%C(yellow)%h%C(auto)%d %Creset%s %C(cyan)[%an] %C(blue)%ar' - edit = !"f() { EDIT_STASH=0 && GIT_SEQUENCE_EDITOR=\"sed -i '' '1s/^pick/edit/'\" git rebase -i \"$1~\" && git reset HEAD~1 && git add -AN && git add -p && git commit -e -C HEAD@{1} && if [ -z \"$(git status --porcelain)\" ]; then git rebase --continue; else git add -A && EDIT_STASH=1 && git stash -q && git rebase --continue && git stash pop -q && EDIT_STASH=0 && git reset; fi || (echo 'Aborting the edit...' && if [ \"$EDIT_STASH\" = \"1\" ]; then git stash drop -q && EDIT_STASH=0; fi && git rebase --abort && git add -A && git reset); }; f" + edit = !~/.dotfiles/bin/git-edit cremit = commit --reedit-message=HEAD sync = "!f() { remote=\"${GIT_UPSTREAM_NAME:-upstream}\"; branch=\"$(git rev-parse --abbrev-ref HEAD)\"; if [ \"$branch\" != \"main\" ] && [ \"$branch\" != \"master\" ]; then echo \"Error: Must be on main or master branch (currently on $branch)\"; return 1; fi; git fetch \"$remote\" --tags || return 1; if [ -n \"$1\" ]; then ref=\"$1\"; sync_branch=\"sync/${ref}\"; else git remote set-head \"$remote\" --auto 2>/dev/null; upstream_branch=\"$(git symbolic-ref \"refs/remotes/$remote/HEAD\" 2>/dev/null | sed \"s|refs/remotes/$remote/||\")\" || { echo \"Error: Could not determine default branch for $remote\"; return 1; }; ref=\"$remote/$upstream_branch\"; sync_branch=\"sync/$(date +%s)-${upstream_branch}\"; fi; git checkout -b \"$sync_branch\" && git merge \"$ref\" -m \"Merge upstream ${ref}\"; }; f" fpush = push --force-with-lease From 307088736e7f17258eb8b474f0d4487f1474dcd7 Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 13:43:32 -0400 Subject: [PATCH 06/10] Move the sync alias into bin/git-sync The "could not determine default branch" check could never fire: it tested the exit status of the sed at the end of a pipeline rather than the symbolic-ref feeding it. An upstream with no default branch got as far as checking out a branch named "sync/-" and failing on a merge of "/". The check now tests for an empty result, so the error it always meant to give actually comes out. Claude-Session: d019263c-0e35-4452-b1c2-f685d76d5c18 --- bin/git-sync | 43 +++++++++++++++++++++++++++++++++++++++++++ git/gitconfig.symlink | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100755 bin/git-sync diff --git a/bin/git-sync b/bin/git-sync new file mode 100755 index 0000000..e2a990d --- /dev/null +++ b/bin/git-sync @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Merge an upstream's changes into a fresh sync branch. +# +# Usage: git sync [ref] +# +# Intended for forks: run it on main/master and it branches off, merges +# the upstream ref, and leaves you on that branch to push and PR. With +# no ref it uses the upstream's default branch. +# +# The upstream remote is $GIT_UPSTREAM_NAME, defaulting to "upstream". + +set -e + +remote="${GIT_UPSTREAM_NAME:-upstream}" +branch="$(git rev-parse --abbrev-ref HEAD)" + +if [ "$branch" != "main" ] && [ "$branch" != "master" ]; then + echo "Error: Must be on main or master branch (currently on $branch)" + exit 1 +fi + +git fetch "$remote" --tags + +if [ -n "$1" ]; then + ref="$1" + sync_branch="sync/${ref}" +else + git remote set-head "$remote" --auto > /dev/null 2>&1 || true + upstream_branch="$(git symbolic-ref "refs/remotes/$remote/HEAD" 2> /dev/null | sed "s|refs/remotes/$remote/||")" + + if [ -z "$upstream_branch" ]; then + echo "Error: Could not determine default branch for $remote" + exit 1 + fi + + ref="$remote/$upstream_branch" + # Timestamped, since the default branch syncs repeatedly. + sync_branch="sync/$(date +%s)-${upstream_branch}" +fi + +git checkout -b "$sync_branch" +git merge "$ref" -m "Merge upstream ${ref}" diff --git a/git/gitconfig.symlink b/git/gitconfig.symlink index 927cf17..ca7bc3f 100644 --- a/git/gitconfig.symlink +++ b/git/gitconfig.symlink @@ -33,7 +33,7 @@ plog = log --oneline --graph --all --decorate --pretty='format:%C(yellow)%h%C(auto)%d %Creset%s %C(cyan)[%an] %C(blue)%ar' edit = !~/.dotfiles/bin/git-edit cremit = commit --reedit-message=HEAD - sync = "!f() { remote=\"${GIT_UPSTREAM_NAME:-upstream}\"; branch=\"$(git rev-parse --abbrev-ref HEAD)\"; if [ \"$branch\" != \"main\" ] && [ \"$branch\" != \"master\" ]; then echo \"Error: Must be on main or master branch (currently on $branch)\"; return 1; fi; git fetch \"$remote\" --tags || return 1; if [ -n \"$1\" ]; then ref=\"$1\"; sync_branch=\"sync/${ref}\"; else git remote set-head \"$remote\" --auto 2>/dev/null; upstream_branch=\"$(git symbolic-ref \"refs/remotes/$remote/HEAD\" 2>/dev/null | sed \"s|refs/remotes/$remote/||\")\" || { echo \"Error: Could not determine default branch for $remote\"; return 1; }; ref=\"$remote/$upstream_branch\"; sync_branch=\"sync/$(date +%s)-${upstream_branch}\"; fi; git checkout -b \"$sync_branch\" && git merge \"$ref\" -m \"Merge upstream ${ref}\"; }; f" + sync = !~/.dotfiles/bin/git-sync fpush = push --force-with-lease track = !git branch --set-upstream-to=origin/$(git symbolic-ref --short HEAD) up = !~/.dotfiles/bin/git-up From 605b191406be10acc580295abcb0cb35a25ff3be Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 13:44:12 -0400 Subject: [PATCH 07/10] Bring system/install.sh in line with conventions It was the one install script with no shebang, no set -e and no shared functions, and its bare ln meant every re-bootstrap failed once $HOME/bin existed. Linking is now conditional, and the apps it opens are guarded so a missing cask warns instead of halting the script under set -e. Claude-Session: d019263c-0e35-4452-b1c2-f685d76d5c18 --- system/install.sh | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/system/install.sh b/system/install.sh index f715330..2ee644d 100755 --- a/system/install.sh +++ b/system/install.sh @@ -1,6 +1,22 @@ +#!/usr/bin/env bash +set -e +source ~/.dotfiles/lib/functions.sh + # Make sure our bin files are placed where we expect (this is added to path in ./path.zsh) -ln -ns ~/.dotfiles/bin $HOME/bin +if [ -L "$HOME/bin" ]; then + info 'bin is already linked' +else + info 'Linking bin' + ln -ns ~/.dotfiles/bin "$HOME/bin" +fi # Open things that need to be opened once in order to boot on load -open /Applications/Rectangle.app -open /Applications/SoundSource.app +for app in Rectangle SoundSource; do + if [ -d "/Applications/$app.app" ]; then + open "/Applications/$app.app" + else + warn "$app is not installed, skipping" + fi +done + +success 'System setup complete' From 8ebe8af70700d184a734186409a19540cc3c29c9 Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 13:45:36 -0400 Subject: [PATCH 08/10] Fence the example block prettier kept reindenting The indented code block inside a list item never reached a fixed point: each run added a couple of spaces, so `npm run format` dirtied the file and `npm run lint` failed on it anyway. Fencing the block gives prettier explicit delimiters and settles it. CI on main was failing on this. Claude-Session: d019263c-0e35-4452-b1c2-f685d76d5c18 --- claude/config/CLAUDE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/claude/config/CLAUDE.md b/claude/config/CLAUDE.md index b707917..68cd288 100644 --- a/claude/config/CLAUDE.md +++ b/claude/config/CLAUDE.md @@ -34,7 +34,9 @@ take precedence where they conflict. nothing else. No braces, brackets, quotes, or other punctuation wrapping either part. Look the title up rather than guessing it. - Issue #142 Fix login redirect loop + ``` + Issue #142 Fix login redirect loop + ``` Not `Issue #142 {Fix login redirect loop}` — any placeholder markup in these instructions indicates text to replace, and never appears in the output. From b8711e3ca7386a075eca7c40d6c5bd9bfecdcbc2 Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 14:25:29 -0400 Subject: [PATCH 09/10] Back up a real bin before linking over it Guarding only on -L meant an existing real $HOME/bin fell through to the ln, which does not fail on a directory: it quietly creates the link inside it as $HOME/bin/bin, exits 0, and set -e never notices. A plain file did fail, aborting the script. Existing paths are now moved to bin.backup first, following the pattern in maestral/install.sh, and a backup that is already taken is an error rather than a second clobber. Claude-Session: d019263c-0e35-4452-b1c2-f685d76d5c18 --- system/install.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/system/install.sh b/system/install.sh index 2ee644d..0b47659 100755 --- a/system/install.sh +++ b/system/install.sh @@ -6,6 +6,16 @@ source ~/.dotfiles/lib/functions.sh if [ -L "$HOME/bin" ]; then info 'bin is already linked' else + # A real bin would otherwise swallow the symlink as $HOME/bin/bin + if [ -e "$HOME/bin" ]; then + if [ -e "$HOME/bin.backup" ]; then + fail 'bin exists and bin.backup is already taken, move one aside first' + fi + warn 'bin exists and will be replaced with a symlink' + info 'Backing up existing bin to bin.backup' + mv "$HOME/bin" "$HOME/bin.backup" + fi + info 'Linking bin' ln -ns ~/.dotfiles/bin "$HOME/bin" fi From 7540e22362f166e0a5c581d8ff6038aab86389fe Mon Sep 17 00:00:00 2001 From: Daniel Schultz Date: Wed, 2 Sep 2026 14:30:19 -0400 Subject: [PATCH 10/10] Restore Claude attribution in settings The attribution block set commit and pr to empty strings and sessionUrl to false, which suppresses attribution entirely and directly contradicts the AI attribution rules in CLAUDE.md. That mismatch is why the commits and PR on this branch went out unattributed and had to be rewritten. Dropping the two empty strings falls back to the standard Claude Code attribution, and sessionUrl is pinned true rather than left to default so a future change of default cannot silently switch it off again. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Uuja2EnY8Wg7UGoWPoyNf5 --- claude/config/settings.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/claude/config/settings.json b/claude/config/settings.json index 887950e..a9751da 100644 --- a/claude/config/settings.json +++ b/claude/config/settings.json @@ -16,9 +16,7 @@ "deny": ["Bash(sed -i *)"] }, "attribution": { - "commit": "", - "pr": "", - "sessionUrl": false + "sessionUrl": true }, "spinnerVerbs": { "mode": "replace",