From d9a537b07f77995e062c0af350993104e6071d17 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Wed, 5 Aug 2026 12:58:21 +0200 Subject: [PATCH 1/7] devops: group the make help output, nothing said what a target was for The help target grepped a flat list, so 30 targets printed as 30 lines with no hint which of them are checks, which install things and which touch $HOME. awk instead of grep+sed: same one-liner shape, plus '##@ Group' section headings parsed out of the banner comments that were already there. claude-settings-test moves up into QA, where it belongs and where qa already called it from - under printed headings it was landing in 'Apps and packages'. --- Makefile | 62 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index 001d2a8..831be87 100644 --- a/Makefile +++ b/Makefile @@ -30,11 +30,17 @@ STOW_FLAGS := --no-folding -t "$$HOME" .PHONY: help help: ## show this help - @grep -E '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed 's/:.*##/\t/' - + @# Two patterns: `##@ Group` opens a section, `## text` after a target + @# documents it. Both are parsed out of this file, so a target is described + @# where it is defined and a target with no `##` stays out of the list. + @awk 'BEGIN {FS = ":.*##"; print "usage: make \n"} \ + /^##@/ { printf "\n%s\n", substr($$0, 5); next } \ + /^[a-zA-Z0-9_.-]+:.*##/ { printf " %-22s %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + +##@ QA # -# QA — every target here is read-only: it must not touch the machine it runs -# on, because CI runs the lot on macOS and Ubuntu on every pull request. +# Every target here is read-only: it must not touch the machine it runs on, +# because CI runs the lot on macOS and Ubuntu on every pull request. # .PHONY: qa qa-deps lint bin-test @@ -71,8 +77,21 @@ bin-test: ## run every *.test.sh — self-contained, no machine state touched echo "== $$t"; bash "$$t" || exit 1; \ done +.PHONY: claude-settings-test +claude-settings-test: ## check the stowed Claude settings parse and stay machine-independent + @# It is one file for every machine, so a path that only exists on one is a + @# bug the other machine finds silently — the setting is simply ignored + @# there. $$HOME instead, and anything genuinely local (a client checkout, a + @# per-machine socket) goes in ~/.claude/settings.local.json, which is not + @# tracked. That file is also why this repo being public is survivable. + @python3 -c 'import json;json.load(open("shared/.claude/settings.json"))' + @grep -nE '"[^"]*/(Users|home)/' shared/.claude/settings.json \ + && { echo "^ only true on one machine - use \$$HOME, or move it to ~/.claude/settings.local.json"; exit 1; } \ + || true + +##@ Security # -# Security — separate from qa because it is the same answer on every OS, so CI +# Separate from qa because it is the same answer on every OS, so CI # runs it once instead of once per matrix leg, and because it needs Docker, # which qa deliberately does not. # @@ -154,8 +173,9 @@ semgrep-mirror: ## copy a semgrep version into my GHCR (once per version bump) @# There is no bash or shell pack in the registry (p/bash and p/shell both @# 404), so `make lint` remains what checks the scripts themselves. +##@ Apps and packages # -# Apps and packages — the Brewfile on every machine, plus whatever the distro +# The Brewfile on every machine, plus whatever the distro # has to supply itself. Called `apps` rather than `brew` because Homebrew is # only most of it: the GUI half on Linux comes from vendor apt repos. # @@ -184,21 +204,7 @@ apps-test: ## parse the Brewfile without installing anything @command -v brew >/dev/null || { echo "brew not installed - skipped"; exit 0; }; \ HOMEBREW_NO_AUTO_UPDATE=1 brew bundle list --file Brewfile >/dev/null -.PHONY: claude-settings-test -claude-settings-test: ## check the stowed Claude settings parse and stay machine-independent - @# It is one file for every machine, so a path that only exists on one is a - @# bug the other machine finds silently — the setting is simply ignored - @# there. $$HOME instead, and anything genuinely local (a client checkout, a - @# per-machine socket) goes in ~/.claude/settings.local.json, which is not - @# tracked. That file is also why this repo being public is survivable. - @python3 -c 'import json;json.load(open("shared/.claude/settings.json"))' - @grep -nE '"[^"]*/(Users|home)/' shared/.claude/settings.json \ - && { echo "^ only true on one machine - use \$$HOME, or move it to ~/.claude/settings.local.json"; exit 1; } \ - || true - -# -# Dotfiles ($HOME) via GNU stow -# +##@ Dotfiles ($HOME) via GNU stow .PHONY: stow restow unstow stow-backup stow-test stow-backup: @@ -256,9 +262,7 @@ stow-test: ## stow and unstow every package in the repo into a throwaway $HOME @t=$$(mktemp -d); trap 'rm -rf "$$t"' EXIT; HOME=$$t $(MAKE) -s stow unstow @# The conflict-backup half is bin/stow/backup.test.sh, which bin-test runs. -# -# Shell -# +##@ Shell .PHONY: shell shell: ## source this repo's .bashrc fragment from ~/.bashrc (idempotent) @@ -272,8 +276,9 @@ shell: ## source this repo's .bashrc fragment from ~/.bashrc (idempotent) && echo "NOTE: an older copy of the fragment is still pasted into ~/.bashrc - delete that block, it shadows the repo" \ || true +##@ JetBrains IDEs # -# JetBrains IDEs — see bin/jetbrains/README.md for why this is a script, not stow +# See bin/jetbrains/README.md for why this is a script, not stow # .PHONY: jetbrains @@ -284,8 +289,9 @@ jetbrains: ## set the JVM options this repo owns in every JetBrains config dir @echo @echo "plugins: open $(CURDIR) in the IDE and accept the 'required plugins' prompt" +##@ macOS System Settings # -# macOS System Settings — the panes stow cannot reach, see bin/macos/defaults.sh +# The panes stow cannot reach, see bin/macos/defaults.sh # .PHONY: macos macos-touchid @@ -314,9 +320,7 @@ macos-touchid: ## authenticate sudo with Touch ID (root-owned, so its own target cat "$$f"; \ sudo -k; echo "now run any sudo command - it should ask for a fingerprint" -# -# GIT config -# +##@ GIT config .PHONY: git git-config-test git-config-format git: ## set up this machine: hook in .gitconfig, set email, set up commit signing From 2c00688a12a5bbbbe9f5489d4db053ab47c885e8 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Sun, 9 Aug 2026 17:40:07 +0200 Subject: [PATCH 2/7] agent: the makefile help convention, it was only ever implied --- shared/.claude/CLAUDE.md | 5 ++-- shared/.claude/rules/makefiles.md | 42 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 shared/.claude/rules/makefiles.md diff --git a/shared/.claude/CLAUDE.md b/shared/.claude/CLAUDE.md index 7e27c64..f464838 100644 --- a/shared/.claude/CLAUDE.md +++ b/shared/.claude/CLAUDE.md @@ -3,8 +3,8 @@ Every rule lives in [`rules/`](rules/), one file each. This file is the index. A rule there loads unconditionally unless it declares `paths:` frontmatter, which scopes it to the files it is about. A rule earns that scoping only when its trigger is a file path *and* breaking it shows up in a diff — GitHub Actions -is the only one so far. The rest override a default I would otherwise fall back -to, so they have to be in context before the mistake, not after. +and Makefiles are the two so far. The rest override a default I would otherwise +fall back to, so they have to be in context before the mistake, not after. | Rule | Applies when | |------|--------------| @@ -14,6 +14,7 @@ to, so they have to be in context before the mistake, not after. | [Commit messages](rules/commit-messages.md) | writing a commit | | [Git history](rules/git-history.md) | amend, squash, rebase, force-push | | [GitHub Actions](rules/github-actions.md) | writing under `.github/` — scoped, loads itself | +| [Makefiles](rules/makefiles.md) | writing a `Makefile` — scoped, loads itself | | [Voice](rules/voice.md) | background, not a rule: the plugin that reads answers aloud | The folder name is not a preference: `rules/` and `CLAUDE.md` are the only two diff --git a/shared/.claude/rules/makefiles.md b/shared/.claude/rules/makefiles.md new file mode 100644 index 0000000..8d7e7d4 --- /dev/null +++ b/shared/.claude/rules/makefiles.md @@ -0,0 +1,42 @@ +--- +paths: + - "**/Makefile" + - "**/makefile" + - "**/GNUmakefile" + - "**/*.mk" +--- + +# Makefiles + +Plain `make` must answer "what can I do here?", so every Makefile gets a `help` +target, and `help` is the **first target in the file** — Make's default goal is +the first target, which is what makes the bare `make` print it. Set +`.DEFAULT_GOAL := help` instead when it cannot be first. + +`help` parses the Makefile rather than repeating it, so a target is described +where it is defined and the list cannot drift from the targets. Two markers: +`## text` at the end of a target line documents that target, `##@ Group` opens a +section. + + ##@ QA + lint: ## parse every shell file without running it + + .PHONY: help + help: ## show this help + @awk 'BEGIN {FS = ":.*##"; print "usage: make \n"} \ + /^##@/ { printf "\n%s\n", substr($$0, 5); next } \ + /^[a-zA-Z0-9_.-]+:.*##/ { printf " %-22s %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + +Group targets by what they are *for*, not by what they call — checks, packages, +dotfiles, deploy — and keep the file in that order, because the section a target +sits under is the section it prints under. + +Prefix a target with its group when the bare name would collide or read +ambiguously across groups. Three checks that each answer "does it parse" cannot +all be `test`, so they become `apps-test`, `stow-test` and `git-config-test`. +Prefix for that reason only: `stow`, `restow` and `unstow` are unambiguous as +they are. + +A target with no `##` is deliberately unlisted — a prerequisite of another +target is not something to reach for by hand. Every target that is not a file is +`.PHONY`. From a34d81557ca06ed6a811edc9dc80469f4f3dc535 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Sun, 9 Aug 2026 17:43:54 +0200 Subject: [PATCH 3/7] devops: colour the help output, the group headings were doing all the work --- Makefile | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 831be87..7bde822 100644 --- a/Makefile +++ b/Makefile @@ -33,9 +33,15 @@ help: ## show this help @# Two patterns: `##@ Group` opens a section, `## text` after a target @# documents it. Both are parsed out of this file, so a target is described @# where it is defined and a target with no `##` stays out of the list. - @awk 'BEGIN {FS = ":.*##"; print "usage: make \n"} \ - /^##@/ { printf "\n%s\n", substr($$0, 5); next } \ - /^[a-zA-Z0-9_.-]+:.*##/ { printf " %-22s %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + @# Colour only when stdout is a tty: piped into less, or read back out of a + @# CI log, the escape codes print as text. Bare \033 rather than tput, which + @# has TERM to be wrong about on exactly the machines that have no tty. + @test -t 1 && tty=1 || tty=0; \ + awk -v tty="$$tty" 'BEGIN { FS = ":.*##"; \ + if (tty) { hdr = "\033[1m"; tgt = "\033[36m"; off = "\033[0m" } \ + print "usage: make \n" } \ + /^##@/ { printf "\n%s%s%s\n", hdr, substr($$0, 5), off; next } \ + /^[a-zA-Z0-9_.-]+:.*##/ { printf " %s%-22s%s %s\n", tgt, $$1, off, $$2 }' $(MAKEFILE_LIST) ##@ QA # From 513aac7c6fa54b58b394bbdaecf53d538ad6c321 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Sun, 9 Aug 2026 17:44:31 +0200 Subject: [PATCH 4/7] devops: spell the QA heading out, an acronym is not what make is asked for --- Makefile | 2 +- shared/.claude/rules/makefiles.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 7bde822..77e6f82 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ help: ## show this help /^##@/ { printf "\n%s%s%s\n", hdr, substr($$0, 5), off; next } \ /^[a-zA-Z0-9_.-]+:.*##/ { printf " %s%-22s%s %s\n", tgt, $$1, off, $$2 }' $(MAKEFILE_LIST) -##@ QA +##@ Quality assurance # # Every target here is read-only: it must not touch the machine it runs on, # because CI runs the lot on macOS and Ubuntu on every pull request. diff --git a/shared/.claude/rules/makefiles.md b/shared/.claude/rules/makefiles.md index 8d7e7d4..7d58f24 100644 --- a/shared/.claude/rules/makefiles.md +++ b/shared/.claude/rules/makefiles.md @@ -18,7 +18,7 @@ where it is defined and the list cannot drift from the targets. Two markers: `## text` at the end of a target line documents that target, `##@ Group` opens a section. - ##@ QA + ##@ Quality assurance lint: ## parse every shell file without running it .PHONY: help @@ -29,7 +29,9 @@ section. Group targets by what they are *for*, not by what they call — checks, packages, dotfiles, deploy — and keep the file in that order, because the section a target -sits under is the section it prints under. +sits under is the section it prints under. Spell the group name out: the heading +is read by someone who does not yet know what `QA` stands for here, which is the +whole reason they ran `make`. Prefix a target with its group when the bare name would collide or read ambiguously across groups. Three checks that each answer "does it parse" cannot From 89b3502b6bc3cbcac77726f5f0ae8973019c3596 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Sun, 9 Aug 2026 17:45:16 +0200 Subject: [PATCH 5/7] devops: a colon after each help heading, printed once not written nine times --- Makefile | 2 +- shared/.claude/rules/makefiles.md | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 77e6f82..5b5ba26 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ help: ## show this help awk -v tty="$$tty" 'BEGIN { FS = ":.*##"; \ if (tty) { hdr = "\033[1m"; tgt = "\033[36m"; off = "\033[0m" } \ print "usage: make \n" } \ - /^##@/ { printf "\n%s%s%s\n", hdr, substr($$0, 5), off; next } \ + /^##@/ { printf "\n%s%s:%s\n", hdr, substr($$0, 5), off; next } \ /^[a-zA-Z0-9_.-]+:.*##/ { printf " %s%-22s%s %s\n", tgt, $$1, off, $$2 }' $(MAKEFILE_LIST) ##@ Quality assurance diff --git a/shared/.claude/rules/makefiles.md b/shared/.claude/rules/makefiles.md index 7d58f24..1a0bbfa 100644 --- a/shared/.claude/rules/makefiles.md +++ b/shared/.claude/rules/makefiles.md @@ -16,16 +16,22 @@ the first target, which is what makes the bare `make` print it. Set `help` parses the Makefile rather than repeating it, so a target is described where it is defined and the list cannot drift from the targets. Two markers: `## text` at the end of a target line documents that target, `##@ Group` opens a -section. +section. The colon after a heading is printed, not written — nine source lines +cannot each remember it. ##@ Quality assurance lint: ## parse every shell file without running it .PHONY: help help: ## show this help - @awk 'BEGIN {FS = ":.*##"; print "usage: make \n"} \ - /^##@/ { printf "\n%s\n", substr($$0, 5); next } \ - /^[a-zA-Z0-9_.-]+:.*##/ { printf " %-22s %s\n", $$1, $$2 }' $(MAKEFILE_LIST) + @# Colour only when stdout is a tty: piped into less, or read back out + @# of a CI log, the escape codes print as text. + @test -t 1 && tty=1 || tty=0; \ + awk -v tty="$$tty" 'BEGIN { FS = ":.*##"; \ + if (tty) { hdr = "\033[1m"; tgt = "\033[36m"; off = "\033[0m" } \ + print "usage: make \n" } \ + /^##@/ { printf "\n%s%s:%s\n", hdr, substr($$0, 5), off; next } \ + /^[a-zA-Z0-9_.-]+:.*##/ { printf " %s%-22s%s %s\n", tgt, $$1, off, $$2 }' $(MAKEFILE_LIST) Group targets by what they are *for*, not by what they call — checks, packages, dotfiles, deploy — and keep the file in that order, because the section a target From b31ce812ad6580fb8aa4d411e28c72acecf3e663 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Sun, 9 Aug 2026 17:56:57 +0200 Subject: [PATCH 6/7] docs: the help example put lint above help, contradicting the rule above it --- Makefile | 12 ++++++------ shared/.claude/rules/makefiles.md | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 5b5ba26..40ad951 100644 --- a/Makefile +++ b/Makefile @@ -97,9 +97,9 @@ claude-settings-test: ## check the stowed Claude settings parse and stay machine ##@ Security # -# Separate from qa because it is the same answer on every OS, so CI -# runs it once instead of once per matrix leg, and because it needs Docker, -# which qa deliberately does not. +# Separate from qa because it is the same answer on every OS, so CI runs it once +# instead of once per matrix leg, and because it needs Docker, which qa +# deliberately does not. # # Pinned: an unpinned scanner turns someone else's rule release into a red build @@ -181,9 +181,9 @@ semgrep-mirror: ## copy a semgrep version into my GHCR (once per version bump) ##@ Apps and packages # -# The Brewfile on every machine, plus whatever the distro -# has to supply itself. Called `apps` rather than `brew` because Homebrew is -# only most of it: the GUI half on Linux comes from vendor apt repos. +# The Brewfile on every machine, plus whatever the distro has to supply itself. +# Called `apps` rather than `brew` because Homebrew is only most of it: the GUI +# half on Linux comes from vendor apt repos. # .PHONY: apps apps-test diff --git a/shared/.claude/rules/makefiles.md b/shared/.claude/rules/makefiles.md index 1a0bbfa..15c1e4d 100644 --- a/shared/.claude/rules/makefiles.md +++ b/shared/.claude/rules/makefiles.md @@ -11,7 +11,9 @@ paths: Plain `make` must answer "what can I do here?", so every Makefile gets a `help` target, and `help` is the **first target in the file** — Make's default goal is the first target, which is what makes the bare `make` print it. Set -`.DEFAULT_GOAL := help` instead when it cannot be first. +`.DEFAULT_GOAL := help` as well, not instead: it is what keeps the bare `make` +answering the day someone adds a target above `help` without noticing that +position was load-bearing. `help` parses the Makefile rather than repeating it, so a target is described where it is defined and the list cannot drift from the targets. Two markers: @@ -19,9 +21,6 @@ where it is defined and the list cannot drift from the targets. Two markers: section. The colon after a heading is printed, not written — nine source lines cannot each remember it. - ##@ Quality assurance - lint: ## parse every shell file without running it - .PHONY: help help: ## show this help @# Colour only when stdout is a tty: piped into less, or read back out @@ -33,6 +32,13 @@ cannot each remember it. /^##@/ { printf "\n%s%s:%s\n", hdr, substr($$0, 5), off; next } \ /^[a-zA-Z0-9_.-]+:.*##/ { printf " %s%-22s%s %s\n", tgt, $$1, off, $$2 }' $(MAKEFILE_LIST) + ##@ Quality assurance + lint: ## parse every shell file without running it + +The parser is deliberately naive — it assumes `##@ ` with the space, one `##` +per line, and a target name inside 22 columns. Widen it when a line in the file +actually breaks it, not in advance. + Group targets by what they are *for*, not by what they call — checks, packages, dotfiles, deploy — and keep the file in that order, because the section a target sits under is the section it prints under. Spell the group name out: the heading @@ -40,8 +46,8 @@ is read by someone who does not yet know what `QA` stands for here, which is the whole reason they ran `make`. Prefix a target with its group when the bare name would collide or read -ambiguously across groups. Three checks that each answer "does it parse" cannot -all be `test`, so they become `apps-test`, `stow-test` and `git-config-test`. +ambiguously across groups. Four checks here each want the name `test`, so they +become `apps-test`, `stow-test`, `git-config-test` and `claude-settings-test`. Prefix for that reason only: `stow`, `restow` and `unstow` are unambiguous as they are. From 50312752aaefba3038cee1a57e9f237279c94767 Mon Sep 17 00:00:00 2001 From: Michal Landsman Date: Sun, 9 Aug 2026 17:59:12 +0200 Subject: [PATCH 7/7] devops: colour the help with four escapes, the tty gate was five lines of ceremony --- Makefile | 14 +++++--------- shared/.claude/rules/makefiles.md | 17 +++++++++-------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 40ad951..8a43e1b 100644 --- a/Makefile +++ b/Makefile @@ -33,15 +33,11 @@ help: ## show this help @# Two patterns: `##@ Group` opens a section, `## text` after a target @# documents it. Both are parsed out of this file, so a target is described @# where it is defined and a target with no `##` stays out of the list. - @# Colour only when stdout is a tty: piped into less, or read back out of a - @# CI log, the escape codes print as text. Bare \033 rather than tput, which - @# has TERM to be wrong about on exactly the machines that have no tty. - @test -t 1 && tty=1 || tty=0; \ - awk -v tty="$$tty" 'BEGIN { FS = ":.*##"; \ - if (tty) { hdr = "\033[1m"; tgt = "\033[36m"; off = "\033[0m" } \ - print "usage: make \n" } \ - /^##@/ { printf "\n%s%s:%s\n", hdr, substr($$0, 5), off; next } \ - /^[a-zA-Z0-9_.-]+:.*##/ { printf " %s%-22s%s %s\n", tgt, $$1, off, $$2 }' $(MAKEFILE_LIST) + @# ponytail: escapes inline, no tty check — `make help | less` shows the + @# codes as text. Gate on `test -t 1` the day anyone pipes this. + @awk 'BEGIN {FS = ":.*##"; print "usage: make \n"} \ + /^##@/ { printf "\n\033[1m%s:\033[0m\n", substr($$0, 5); next } \ + /^[a-zA-Z0-9_.-]+:.*##/ { printf " \033[36m%-22s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) ##@ Quality assurance # diff --git a/shared/.claude/rules/makefiles.md b/shared/.claude/rules/makefiles.md index 15c1e4d..682de6b 100644 --- a/shared/.claude/rules/makefiles.md +++ b/shared/.claude/rules/makefiles.md @@ -23,18 +23,19 @@ cannot each remember it. .PHONY: help help: ## show this help - @# Colour only when stdout is a tty: piped into less, or read back out - @# of a CI log, the escape codes print as text. - @test -t 1 && tty=1 || tty=0; \ - awk -v tty="$$tty" 'BEGIN { FS = ":.*##"; \ - if (tty) { hdr = "\033[1m"; tgt = "\033[36m"; off = "\033[0m" } \ - print "usage: make \n" } \ - /^##@/ { printf "\n%s%s:%s\n", hdr, substr($$0, 5), off; next } \ - /^[a-zA-Z0-9_.-]+:.*##/ { printf " %s%-22s%s %s\n", tgt, $$1, off, $$2 }' $(MAKEFILE_LIST) + @awk 'BEGIN {FS = ":.*##"; print "usage: make \n"} \ + /^##@/ { printf "\n\033[1m%s:\033[0m\n", substr($$0, 5); next } \ + /^[a-zA-Z0-9_.-]+:.*##/ { printf " \033[36m%-22s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) ##@ Quality assurance lint: ## parse every shell file without running it +Bold the heading, colour the target name: 30 targets in one column is the thing +the grouping was meant to fix, and the section titles have to win over the rows +under them. The escapes go inline in the format string rather than through +`tput`, and there is no `test -t 1` gate — piping `make help` into `less` shows +the codes as text, which is a cost worth one line of awk rather than five. + The parser is deliberately naive — it assumes `##@ ` with the space, one `##` per line, and a target name inside 22 columns. Widen it when a line in the file actually breaks it, not in advance.