feat(setup): persistent timeline Stop hook opt-out — timeline_stop_hook config gate (#2677)

--no-team is a one-shot teardown, so every later bare ./setup (including the
ones /gstack-upgrade runs) re-registered the timeline Stop hook with no way
to say 'never'. New gate mirrors the plan_tune_hooks pattern: flag
(--timeline-stop-hook/--no-timeline-stop-hook) > env
(GSTACK_TIMELINE_STOP_HOOK) > saved config (timeline_stop_hook) > default
yes. An explicit flag persists to config so the decision survives upgrades;
an explicit 'no' also removes a live registration (reconciliation), so the
opt-out works against installs registered by an older setup. --no-team
semantics unchanged (NO_TEAM_MODE is never initialized from config). Full
gstack-config surface: DEFAULTS entry, header docs, list/defaults
enumeration, warn-and-default validation.

Fixes #2677

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-31 21:26:55 +00:00
co-authored by Claude Fable 5
parent 7ed5e87bba
commit 079b36c7f8
3 changed files with 147 additions and 3 deletions
+17 -2
View File
@@ -93,6 +93,14 @@ CONFIG_HEADER='# gstack configuration — edit freely, changes take effect on ne
# # Set to true once the privacy gate has asked the user.
# # Flip back to false to be re-prompted.
#
# ─── Timeline Stop hook ──────────────────────────────────────────────
# timeline_stop_hook: yes # Controls whether ./setup registers the timeline
# # Stop hook (closes dangling session entries).
# # yes — register on every setup (default)
# # no — never register; setup also removes a
# # live registration (persistent opt-out;
# # --no-team stays a one-shot teardown, #2677)
#
# ─── Plan-tune hooks ─────────────────────────────────────────────────
# plan_tune_hooks: prompt # Controls whether ./setup installs the plan-tune
# # Claude Code hooks (PostToolUse capture +
@@ -148,6 +156,7 @@ lookup_default() {
artifacts_sync_mode) echo "off" ;;
artifacts_sync_mode_prompted) echo "false" ;;
plan_tune_hooks) echo "prompt" ;; # prompt | yes | no — controls ./setup plan-tune hook install
timeline_stop_hook) echo "yes" ;; # yes | no — controls ./setup timeline Stop hook registration (#2677)
redact_repo_visibility) echo "" ;; # empty → fall through to gh/glab detection
redact_prepush_hook) echo "false" ;;
@@ -396,6 +405,10 @@ case "${1:-}" in
echo "Warning: plan_tune_hooks '$VALUE' not recognized. Valid values: prompt, yes, no. Using prompt." >&2
VALUE="prompt"
fi
if [ "$KEY" = "timeline_stop_hook" ] && [ "$VALUE" != "yes" ] && [ "$VALUE" != "no" ]; then
echo "Warning: timeline_stop_hook '$VALUE' not recognized. Valid values: yes, no. Using yes." >&2
VALUE="yes"
fi
# codex_reviews controls PAID Codex calls. Unlike the warn-and-default keys above,
# an invalid value is REJECTED and the existing setting is left unchanged — a typo
# must never silently flip the switch and turn paid Codex calls on or off.
@@ -441,7 +454,8 @@ case "${1:-}" in
for KEY in proactive routing_declined telemetry auto_upgrade update_check \
skill_prefix checkpoint_mode checkpoint_push explain_level \
codex_reviews gstack_contributor skip_eng_review workspace_root \
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks; do
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks \
timeline_stop_hook; do
VALUE=$(read_config_value "$KEY" || true)
SOURCE="default"
if [ -n "$VALUE" ]; then
@@ -457,7 +471,8 @@ case "${1:-}" in
for KEY in proactive routing_declined telemetry auto_upgrade update_check \
skill_prefix checkpoint_mode checkpoint_push explain_level \
codex_reviews gstack_contributor skip_eng_review workspace_root \
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks; do
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks \
timeline_stop_hook; do
printf ' %-24s %s\n' "$KEY:" "$(lookup_default "$KEY")"
done
;;