mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 12:27:45 +01:00
Make the recovery scripts say why they failed, not just print it
The three helper scripts were only ever dry-run, so the no-silent-failures hook never saw them until they were committed. Six handlers reported the reason to stdout via _report() and nothing else — invisible to the log, and easy to miss when the script runs as one step of finish_2026_08_24.sh amid a wall of other output. These mutate benefit and sick-day state, so a quiet failure there reads as success. Log at error/warning alongside the stdout line, saying what was not done: the bonus was not banked, the revocation was not persisted, the credit gate cannot tell and so fails closed. Verified by feeding the gate a corrupt log: it now logs the reason with a traceback and exits 1, instead of printing one line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EJmPLyT8NU4DzDJPV1KEX5
This commit is contained in:
parent
16ada6e5a2
commit
f4850670ee
@ -28,6 +28,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
@ -37,6 +38,12 @@ EXTRA_BENEFITS_FILE = REPO_ROOT / "screen_locker" / "extra_benefits_state.json"
|
||||
|
||||
DEFAULT_BONUS_HOURS = 2
|
||||
|
||||
# Failures go to the log (stderr) as well as stdout: this script runs as one
|
||||
# step of finish_2026_08_24.sh, where a bare stdout line scrolls past in a wall
|
||||
# of other output, and it mutates benefit state — the exact case where a quiet
|
||||
# failure looks like success.
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _report(message: str) -> None:
|
||||
"""Write one line to stdout (matches the other scripts in this directory)."""
|
||||
@ -57,6 +64,7 @@ def _load(path: Path) -> dict:
|
||||
try:
|
||||
return json.loads(path.read_text())
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
_logger.warning("Could not read %s: %s — treating as empty", path, exc)
|
||||
_report(f"WARN: could not read {path}: {exc} — treating as empty")
|
||||
return {}
|
||||
|
||||
@ -119,6 +127,10 @@ def main() -> int:
|
||||
try:
|
||||
Path(args.benefits_file).write_text(json.dumps(benefits, indent=2) + "\n")
|
||||
except OSError as exc:
|
||||
_logger.exception(
|
||||
"Could not write %s — the bonus was NOT banked",
|
||||
args.benefits_file,
|
||||
)
|
||||
_report(f"FAIL: could not write {args.benefits_file}: {exc}")
|
||||
return 1
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
@ -31,6 +32,11 @@ REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
SICK_HISTORY_FILE = REPO_ROOT / "screen_locker" / "sick_history.json"
|
||||
LOG_FILE = REPO_ROOT / "screen_locker" / "log.json"
|
||||
|
||||
# Failures are logged as well as printed: this script rewrites sick-day
|
||||
# history, so a read/write error that only reaches stdout can leave the
|
||||
# revocation half-applied while the surrounding script reports success.
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _report(message: str) -> None:
|
||||
"""Write one line to stdout (matches the other scripts in this directory)."""
|
||||
@ -112,6 +118,7 @@ def main() -> int:
|
||||
try:
|
||||
history = json.loads(history_path.read_text())
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
_logger.exception("Could not read %s — no sick day was revoked", history_path)
|
||||
_report(f"FAIL: could not read {history_path}: {exc}")
|
||||
return 1
|
||||
|
||||
@ -119,6 +126,10 @@ def main() -> int:
|
||||
try:
|
||||
log = json.loads(log_path.read_text()) if log_path.is_file() else {}
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
_logger.exception(
|
||||
"Could not read %s — cannot cite a workout id, so nothing was revoked",
|
||||
log_path,
|
||||
)
|
||||
_report(f"FAIL: could not read {log_path}: {exc}")
|
||||
return 1
|
||||
|
||||
@ -133,6 +144,10 @@ def main() -> int:
|
||||
try:
|
||||
history_path.write_text(json.dumps(history, indent=2) + "\n")
|
||||
except OSError as exc:
|
||||
_logger.exception(
|
||||
"Could not write %s — the revocation was NOT persisted",
|
||||
history_path,
|
||||
)
|
||||
_report(f"FAIL: could not write {history_path}: {exc}")
|
||||
return 1
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
@ -38,6 +39,12 @@ COUNTED_WORKOUT_TYPES = frozenset(
|
||||
)
|
||||
|
||||
|
||||
# This script is a gate: it decides whether the rest of the recovery may
|
||||
# proceed. A read failure that only reaches stdout can be mistaken for a plain
|
||||
# "not credited" verdict, so it is logged loudly as well.
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _report(message: str) -> None:
|
||||
"""Write one line to stdout (matches the other scripts in this directory)."""
|
||||
sys.stdout.write(f"{message}\n")
|
||||
@ -58,6 +65,11 @@ def main() -> int:
|
||||
try:
|
||||
log = json.loads(log_path.read_text())
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
_logger.exception(
|
||||
"Could not read %s — cannot tell whether the workout was "
|
||||
"credited, so this gate fails closed",
|
||||
log_path,
|
||||
)
|
||||
_report(f"FAIL: could not read {log_path}: {exc}")
|
||||
return 1
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user