From 82ba704957533c8f3e2f83657fe77e26cffc77bb Mon Sep 17 00:00:00 2001 From: christian-westerkam Date: Wed, 21 Oct 2026 00:12:37 +0200 Subject: [PATCH 1/5] Document branch-and-PR workflow; never write to main Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 2117ff2..e3dbd86 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,5 +12,14 @@ A tiny zero-dependency Node.js todo CLI, used as a sandbox for trying Claude Cod - Every behavior change gets a test in `test/`. - Commit messages: short imperative subject line. +## Workflow (required) +Never commit, merge, or push to `main`. Only a human (Christian) merges into `main`. +1. Branch off an up-to-date `main` before any change: `git switch -c /` (e.g. `fix/duplicate-ids`, `docs/readme`). +2. Commit on that branch; run `npm test` before pushing. +3. Push the branch and open a PR against `main`. +4. Run `/code-review` on the PR branch. +5. Post the result as a PR comment that starts with either **Approved** or **Needs fixes**, followed by the findings. If it needs fixes, fix them on the same branch, push, and repeat steps 4–5. +6. Stop there. Do not merge the PR, even if approved — tell the human it's ready. + ## Remote Gitea at code.westerkam.org — `origin` = `git@code.westerkam.org:christian/claude-playground.git`. -- 2.43.0 From 962c1aa9a1b22a7e9fceaa704c2d7862a06bdc7e Mon Sep 17 00:00:00 2001 From: christian-westerkam Date: Wed, 21 Oct 2026 00:22:48 +0200 Subject: [PATCH 2/5] Address review: fresh base, named review target, round cap, Gitea API Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e3dbd86..f1cb91e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,12 +14,27 @@ A tiny zero-dependency Node.js todo CLI, used as a sandbox for trying Claude Cod ## Workflow (required) Never commit, merge, or push to `main`. Only a human (Christian) merges into `main`. -1. Branch off an up-to-date `main` before any change: `git switch -c /` (e.g. `fix/duplicate-ids`, `docs/readme`). -2. Commit on that branch; run `npm test` before pushing. -3. Push the branch and open a PR against `main`. -4. Run `/code-review` on the PR branch. -5. Post the result as a PR comment that starts with either **Approved** or **Needs fixes**, followed by the findings. If it needs fixes, fix them on the same branch, push, and repeat steps 4–5. -6. Stop there. Do not merge the PR, even if approved — tell the human it's ready. +This is also enforced outside this file: Gitea branch protection on `main` and a deny rule in `.claude/settings.json`. +1. Start every change from a fresh `main`: `git fetch origin && git switch -c / origin/main` (e.g. `fix/duplicate-ids`, `docs/readme`). +2. Commit on that branch; run `npm test` before every push. +3. Push the branch and open a PR against `main` (see "Gitea API" below). +4. Review it with `/code-review `. Always name the branch: with no target, the review diffs against the just-pushed upstream and sees nothing. +5. Post the result as a PR comment. Its first line is **Approved** or **Needs fixes**, its second is `_Automated self-review by Claude; not an independent review._`, then the findings. +6. If it needs fixes, fix the correctness findings on the same branch and repeat steps 2–5. At most 2 fix rounds: after that, post what's left and hand off to the human. +7. Stop there. Do not merge the PR, even if approved; tell the human it's ready. + +### Gitea API +No `gh`/`tea`: use `curl` with `$GITEA_TOKEN` (set in the git-ignored `.claude/settings.local.json`). If it's unset, stop and ask the human; don't work around it. +```sh +API=https://code.westerkam.org/api/v1/repos/christian/claude-playground +# Open a PR +jq -n --arg head "$BRANCH" --arg title "$TITLE" --arg body "$BODY" '{head:$head, base:"main", title:$title, body:$body}' \ + | curl -s -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/pulls" +# Comment on PR number $N +jq -n --rawfile body comment.md '{body:$body}' \ + | curl -s -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/issues/$N/comments" +``` +Run `git commit` and `git push` as standalone commands (no `cd … &&` prefix): only then do they run outside the sandbox, which commit signing needs. ## Remote Gitea at code.westerkam.org — `origin` = `git@code.westerkam.org:christian/claude-playground.git`. -- 2.43.0 From fa45c58c0c92d13763eeb1ba2953a3f659a63e0d Mon Sep 17 00:00:00 2001 From: christian-westerkam Date: Wed, 21 Oct 2026 00:32:15 +0200 Subject: [PATCH 3/5] Address review round 2: no-track branches, honest enforcement, safer API calls Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f1cb91e..adeb8ac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,27 +14,31 @@ A tiny zero-dependency Node.js todo CLI, used as a sandbox for trying Claude Cod ## Workflow (required) Never commit, merge, or push to `main`. Only a human (Christian) merges into `main`. -This is also enforced outside this file: Gitea branch protection on `main` and a deny rule in `.claude/settings.json`. -1. Start every change from a fresh `main`: `git fetch origin && git switch -c / origin/main` (e.g. `fix/duplicate-ids`, `docs/readme`). +These rules are instructions, not a lock. Gitea branch protection on `main` is the real guard; the `git push … main` deny rules in `.claude/settings.json` only catch the obvious spellings. +1. Start every change from a fresh `main`. Run `git fetch origin` as its own command, then `git switch --no-track -c / origin/main` (e.g. `fix/duplicate-ids`, `docs/readme`). `--no-track` keeps the branch from tracking `main`. 2. Commit on that branch; run `npm test` before every push. -3. Push the branch and open a PR against `main` (see "Gitea API" below). -4. Review it with `/code-review `. Always name the branch: with no target, the review diffs against the just-pushed upstream and sees nothing. -5. Post the result as a PR comment. Its first line is **Approved** or **Needs fixes**, its second is `_Automated self-review by Claude; not an independent review._`, then the findings. +3. Push with `git push -u origin HEAD`, then open a PR against `main` (see "Gitea API" below). +4. Review it with `/code-review `. Always name the branch, so the review diffs it against `main` whatever its upstream is. +5. Post the result as a PR comment. The first line is the verdict: **Needs fixes** if there is any correctness finding, otherwise **Approved** (cleanup-only findings are listed but don't block). The second line is `_Automated self-review by Claude; not an independent review._`, followed by the findings. 6. If it needs fixes, fix the correctness findings on the same branch and repeat steps 2–5. At most 2 fix rounds: after that, post what's left and hand off to the human. 7. Stop there. Do not merge the PR, even if approved; tell the human it's ready. ### Gitea API -No `gh`/`tea`: use `curl` with `$GITEA_TOKEN` (set in the git-ignored `.claude/settings.local.json`). If it's unset, stop and ask the human; don't work around it. +No `gh`/`tea`: use `curl` with `$GITEA_TOKEN`. One-time human setup in the git-ignored `.claude/settings.local.json`: set `env.GITEA_TOKEN` and add `code.westerkam.org` to `sandbox.network.allowedDomains`. Settings load at session start, so after adding them the session must be restarted (`claude --continue`). If the token is unset, stop and ask the human; don't work around it. + +Shell state doesn't persist between commands, so set every variable in the same command as the call. Write comment bodies to `$TMPDIR`, never into the repo. `-fsS` makes HTTP errors fail loudly; check that the PR response has a `.number`. ```sh API=https://code.westerkam.org/api/v1/repos/christian/claude-playground # Open a PR +BRANCH=fix/x TITLE="Fix x" BODY="…" jq -n --arg head "$BRANCH" --arg title "$TITLE" --arg body "$BODY" '{head:$head, base:"main", title:$title, body:$body}' \ - | curl -s -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/pulls" + | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/pulls" | jq -e .number # Comment on PR number $N -jq -n --rawfile body comment.md '{body:$body}' \ - | curl -s -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/issues/$N/comments" +N=1 +jq -n --rawfile body "$TMPDIR/comment.md" '{body:$body}' \ + | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/issues/$N/comments" | jq -r .html_url ``` -Run `git commit` and `git push` as standalone commands (no `cd … &&` prefix): only then do they run outside the sandbox, which commit signing needs. +Run `git fetch`, `git commit` and `git push` as standalone commands (no `cd … &&` prefix, nothing chained after them): only then do they run outside the sandbox, which reaching the remote and commit signing need. ## Remote Gitea at code.westerkam.org — `origin` = `git@code.westerkam.org:christian/claude-playground.git`. -- 2.43.0 From ed197eeeb14bf306c5bd78b1bbdfa2ce4e9f448f Mon Sep 17 00:00:00 2001 From: christian-westerkam Date: Wed, 21 Oct 2026 00:50:38 +0200 Subject: [PATCH 4/5] Address review round 3: safe API snippets, PR opened once, pinned review - Titles and bodies go through --rawfile files, never inline shell quotes - pipefail + curl -f + jq -e so a failed call can't look successful - Fix rounds merge latest main and skip re-opening the PR - Check the branch isn't main before pushing; never bypass the hooks - Review at a pinned level; only correctness findings checked against the code block approval Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index adeb8ac..7bead44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,31 +14,43 @@ A tiny zero-dependency Node.js todo CLI, used as a sandbox for trying Claude Cod ## Workflow (required) Never commit, merge, or push to `main`. Only a human (Christian) merges into `main`. -These rules are instructions, not a lock. Gitea branch protection on `main` is the real guard; the `git push … main` deny rules in `.claude/settings.json` only catch the obvious spellings. -1. Start every change from a fresh `main`. Run `git fetch origin` as its own command, then `git switch --no-track -c / origin/main` (e.g. `fix/duplicate-ids`, `docs/readme`). `--no-track` keeps the branch from tracking `main`. -2. Commit on that branch; run `npm test` before every push. -3. Push with `git push -u origin HEAD`, then open a PR against `main` (see "Gitea API" below). -4. Review it with `/code-review `. Always name the branch, so the review diffs it against `main` whatever its upstream is. -5. Post the result as a PR comment. The first line is the verdict: **Needs fixes** if there is any correctness finding, otherwise **Approved** (cleanup-only findings are listed but don't block). The second line is `_Automated self-review by Claude; not an independent review._`, followed by the findings. -6. If it needs fixes, fix the correctness findings on the same branch and repeat steps 2–5. At most 2 fix rounds: after that, post what's left and hand off to the human. -7. Stop there. Do not merge the PR, even if approved; tell the human it's ready. +This is enforced by the git hooks that `npm install` installs and by Gitea branch protection (see "Protecting `main`" in the README). Never get around them: no `--no-verify`, no changing `core.hooksPath`, no editing `.git/`. +1. **Branch.** Run `git fetch origin` on its own, then `git switch --no-track -c / origin/main` (e.g. `fix/duplicate-ids`, `docs/readme`). `--no-track` stops the branch from tracking `main`. +2. **Commit and test.** Commit on the branch; `npm test` must pass before every push. +3. **Push.** Check that `git branch --show-current` isn't `main`, then run `git push -u origin HEAD`. +4. **Open the PR**, once per branch (see "Gitea API" below). +5. **Review** with `/code-review medium`. Name both the branch and the level, so every round diffs against `main` at the same depth. +6. **Comment** on the PR with the verdict. + - First line: **Needs fixes** if any correctness finding still holds after you check it against the code; otherwise **Approved**. Cleanup and judgement-call findings are listed but don't block. + - Second line: `_Automated self-review by Claude; not an independent review._` + - Then the findings, including any you checked and rejected, and why. +7. **Fix rounds.** If it needs fixes, first bring in the latest `main`: run `git fetch origin`, then `git merge --no-ff --no-commit origin/main`, then `git commit --no-edit`, each as its own command. Don't rebase, because force pushes are denied. Fix the correctness findings, then repeat steps 2, 3, 5 and 6 (not step 4: the PR already exists). Do at most 2 fix rounds, then post what's left and hand off. +8. **Stop.** Never merge, even if approved; tell the human it's ready. ### Gitea API -No `gh`/`tea`: use `curl` with `$GITEA_TOKEN`. One-time human setup in the git-ignored `.claude/settings.local.json`: set `env.GITEA_TOKEN` and add `code.westerkam.org` to `sandbox.network.allowedDomains`. Settings load at session start, so after adding them the session must be restarted (`claude --continue`). If the token is unset, stop and ask the human; don't work around it. +No `gh`/`tea`: use `curl` with `$GITEA_TOKEN`. -Shell state doesn't persist between commands, so set every variable in the same command as the call. Write comment bodies to `$TMPDIR`, never into the repo. `-fsS` makes HTTP errors fail loudly; check that the PR response has a `.number`. +One-time human setup in the git-ignored `.claude/settings.local.json`: set `env.GITEA_TOKEN`, and add `code.westerkam.org` to `sandbox.network.allowedDomains`. Settings load at session start, so restart the session after adding them (`claude --continue`). If the token is unset, stop and ask the human; don't work around it. + +Rules for every API call: +- Make each call one self-contained command, because shell state doesn't carry over between commands. +- Write titles and bodies to files in `$TMPDIR` (never in the repo) and pass them with `--rawfile`. Never put them inline in double quotes, where backticks and `$(…)` would run. +- `pipefail`, `curl -f` and `jq -e` make every failure exit non-zero. ```sh -API=https://code.westerkam.org/api/v1/repos/christian/claude-playground -# Open a PR -BRANCH=fix/x TITLE="Fix x" BODY="…" -jq -n --arg head "$BRANCH" --arg title "$TITLE" --arg body "$BODY" '{head:$head, base:"main", title:$title, body:$body}' \ - | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/pulls" | jq -e .number -# Comment on PR number $N -N=1 +# Open a PR for the current branch (once). Write $TMPDIR/pr-title.txt and $TMPDIR/pr-body.md first. +set -o pipefail; API=https://code.westerkam.org/api/v1/repos/christian/claude-playground +jq -n --arg head "$(git branch --show-current)" --rawfile title "$TMPDIR/pr-title.txt" --rawfile body "$TMPDIR/pr-body.md" \ + '{head:$head, base:"main", title:($title|rtrimstr("\n")), body:$body}' \ + | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/pulls" \ + | jq -er '"#\(.number) \(.html_url)"' + +# Comment on PR . Write $TMPDIR/comment.md first. +set -o pipefail; API=https://code.westerkam.org/api/v1/repos/christian/claude-playground jq -n --rawfile body "$TMPDIR/comment.md" '{body:$body}' \ - | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/issues/$N/comments" | jq -r .html_url + | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/issues//comments" \ + | jq -er .html_url ``` -Run `git fetch`, `git commit` and `git push` as standalone commands (no `cd … &&` prefix, nothing chained after them): only then do they run outside the sandbox, which reaching the remote and commit signing need. +Run `git fetch`, `git commit` and `git push` as standalone commands, with no `cd … &&` before them and nothing chained after. Only then do they run outside the sandbox, which they need to reach the remote and to sign commits. ## Remote Gitea at code.westerkam.org — `origin` = `git@code.westerkam.org:christian/claude-playground.git`. -- 2.43.0 From f666bdf6b3ad1ce1b7d0b68ab2bd5982c7f4f35c Mon Sep 17 00:00:00 2001 From: christian-westerkam Date: Wed, 21 Oct 2026 00:56:51 +0200 Subject: [PATCH 5/5] Give the PR workflow a hard stop Only confirmed correctness bugs block approval; everything else is a note. At most one fix round, then hand off. Don't widen scope to satisfy a review. Merge main only on conflict or request. The token goes in a private header file, and PR responses are checked for a number. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 52 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7bead44..4f4674e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,44 +13,58 @@ A tiny zero-dependency Node.js todo CLI, used as a sandbox for trying Claude Cod - Commit messages: short imperative subject line. ## Workflow (required) -Never commit, merge, or push to `main`. Only a human (Christian) merges into `main`. -This is enforced by the git hooks that `npm install` installs and by Gitea branch protection (see "Protecting `main`" in the README). Never get around them: no `--no-verify`, no changing `core.hooksPath`, no editing `.git/`. -1. **Branch.** Run `git fetch origin` on its own, then `git switch --no-track -c / origin/main` (e.g. `fix/duplicate-ids`, `docs/readme`). `--no-track` stops the branch from tracking `main`. -2. **Commit and test.** Commit on the branch; `npm test` must pass before every push. -3. **Push.** Check that `git branch --show-current` isn't `main`, then run `git push -u origin HEAD`. -4. **Open the PR**, once per branch (see "Gitea API" below). -5. **Review** with `/code-review medium`. Name both the branch and the level, so every round diffs against `main` at the same depth. -6. **Comment** on the PR with the verdict. - - First line: **Needs fixes** if any correctness finding still holds after you check it against the code; otherwise **Approved**. Cleanup and judgement-call findings are listed but don't block. +Never commit, merge, or push to `main`. Only a human (Christian) merges into `main`, in Gitea. +The git hooks installed by `npm install` and Gitea branch protection enforce this (see "Protecting `main`" in the README). Never get around them: no `--no-verify`, no changing `core.hooksPath`, no editing `.git/`. + +1. **Branch.** Run `git fetch origin` on its own, then `git switch --no-track -c / origin/main` (e.g. `fix/duplicate-ids`). +2. **Commit, test, push.** `npm test` must pass. Check that `git branch --show-current` isn't `main`, then run `git push -u origin HEAD`. +3. **Open the PR**, once per branch (see "Gitea API"). +4. **Review** with `/code-review medium`. +5. **Triage.** The review lists unverified candidates, and it will always find some. Check each one against the code. A finding **blocks** only if it is a correctness bug in this PR's change that you have confirmed, ideally with a test that fails. Everything else is a **note**: cleanups, judgement calls, hardening ideas and unconfirmed findings. +6. **Comment** on the PR: + - First line: **Needs fixes** if anything blocks, otherwise **Approved**. - Second line: `_Automated self-review by Claude; not an independent review._` - - Then the findings, including any you checked and rejected, and why. -7. **Fix rounds.** If it needs fixes, first bring in the latest `main`: run `git fetch origin`, then `git merge --no-ff --no-commit origin/main`, then `git commit --no-edit`, each as its own command. Don't rebase, because force pushes are denied. Fix the correctness findings, then repeat steps 2, 3, 5 and 6 (not step 4: the PR already exists). Do at most 2 fix rounds, then post what's left and hand off. -8. **Stop.** Never merge, even if approved; tell the human it's ready. + - Then the blocking findings, then the notes, then the findings you rejected, each with the reason. +7. **At most one fix round.** If the PR needs fixes, fix only the blocking findings, each with a test. Push, review once more, and post the verdict from steps 5–6. Then stop, whatever that second review says. Findings about the fixes themselves go to the human as notes; don't fix them. +8. **Hand off.** Never merge, even if the PR is approved. Tell the human it's ready, with the verdict and any notes. + +Don't widen scope beyond the PR's purpose to satisfy a review. If a fix would add significant new code (a new mechanism, not a correction), list it as a note for the human instead. + +Bring in the latest `main` only when the PR conflicts or the human asks: +1. Run `git fetch origin`. +2. Run `git merge --no-ff --no-commit origin/main`. +3. If it says "Already up to date", stop here. Otherwise resolve any conflicts and run `npm test`. +4. Run `git commit --no-edit`. + +Run each as its own command, and never rebase or force-push. ### Gitea API No `gh`/`tea`: use `curl` with `$GITEA_TOKEN`. -One-time human setup in the git-ignored `.claude/settings.local.json`: set `env.GITEA_TOKEN`, and add `code.westerkam.org` to `sandbox.network.allowedDomains`. Settings load at session start, so restart the session after adding them (`claude --continue`). If the token is unset, stop and ask the human; don't work around it. +One-time human setup in the git-ignored `.claude/settings.local.json`: set `env.GITEA_TOKEN`, and add `code.westerkam.org` to `sandbox.network.allowedDomains`. Then restart the session (`claude --continue`); settings load only at start. If the token is unset, stop and ask the human. Rules for every API call: - Make each call one self-contained command, because shell state doesn't carry over between commands. -- Write titles and bodies to files in `$TMPDIR` (never in the repo) and pass them with `--rawfile`. Never put them inline in double quotes, where backticks and `$(…)` would run. -- `pipefail`, `curl -f` and `jq -e` make every failure exit non-zero. +- Put titles and bodies in files in `$TMPDIR` (never in the repo) and pass them with `--rawfile`, never inline in double quotes, where backticks and `$(…)` would run. +- The token goes in a private header file, so it doesn't show up in the process list. +- `pipefail`, `curl -f` and the `jq` checks make every failure exit non-zero. ```sh # Open a PR for the current branch (once). Write $TMPDIR/pr-title.txt and $TMPDIR/pr-body.md first. set -o pipefail; API=https://code.westerkam.org/api/v1/repos/christian/claude-playground +(umask 077; printf 'Authorization: token %s\n' "$GITEA_TOKEN" > "$TMPDIR/gitea-auth") jq -n --arg head "$(git branch --show-current)" --rawfile title "$TMPDIR/pr-title.txt" --rawfile body "$TMPDIR/pr-body.md" \ '{head:$head, base:"main", title:($title|rtrimstr("\n")), body:$body}' \ - | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/pulls" \ - | jq -er '"#\(.number) \(.html_url)"' + | curl -fsS -X POST -H @"$TMPDIR/gitea-auth" -H "Content-Type: application/json" -d @- "$API/pulls" \ + | jq -er 'if .number then "#\(.number) \(.html_url)" else error("no PR number in response") end' # Comment on PR . Write $TMPDIR/comment.md first. set -o pipefail; API=https://code.westerkam.org/api/v1/repos/christian/claude-playground +(umask 077; printf 'Authorization: token %s\n' "$GITEA_TOKEN" > "$TMPDIR/gitea-auth") jq -n --rawfile body "$TMPDIR/comment.md" '{body:$body}' \ - | curl -fsS -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d @- "$API/issues//comments" \ + | curl -fsS -X POST -H @"$TMPDIR/gitea-auth" -H "Content-Type: application/json" -d @- "$API/issues//comments" \ | jq -er .html_url ``` -Run `git fetch`, `git commit` and `git push` as standalone commands, with no `cd … &&` before them and nothing chained after. Only then do they run outside the sandbox, which they need to reach the remote and to sign commits. +Commands listed under `sandbox.excludedCommands` in `.claude/settings.json` (`git fetch`, `git commit`, `git push` and others) run outside the sandbox only when they are run standalone: no `cd … &&` before them and nothing chained after. They need that to reach the remote and to sign commits. ## Remote Gitea at code.westerkam.org — `origin` = `git@code.westerkam.org:christian/claude-playground.git`. -- 2.43.0