Skip to content

feat(hygiene): structural prevention for tick-history row-ordering bug — last-row-is-latest CI check + append helper#532

Merged
AceHack merged 1 commit intomainfrom
feat/tick-history-monotonic-order-check-prevent-row-ordering-bug
Apr 26, 2026
Merged

feat(hygiene): structural prevention for tick-history row-ordering bug — last-row-is-latest CI check + append helper#532
AceHack merged 1 commit intomainfrom
feat/tick-history-monotonic-order-check-prevent-row-ordering-bug

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented Apr 26, 2026

Summary

Aaron 2026-04-26 — "anything we can do to prevent it in the first place?" — after I caught the recurring tick-history row-ordering bug yet again this tick.

Bug shape: Edit tool's old_string=existing-line pattern tends to insert NEW row BEFORE the matched line, producing reverse-chronological order in `docs/hygiene-history/loop-tick-history.md`. I've caught this at least three times across recent ticks; relying on agent vigilance hasn't worked.

Structural fix: three additive layers.

1. CI gate — `tools/hygiene/check-tick-history-order.sh`

Validates that the LAST row in the file is the LATEST timestamp. Catches the specific bug shape (new row inserted before previous last row) without requiring history-rewrite (Otto-229 forbids editing prior rows).

Two-tier:

  • Default: quiet on historical strict-order violations; fails ONLY on the last-row check
  • `--strict`: reports historical violations as advisory (for human spelunking)

Per Aaron's specific guidance — "we might should allow this one override if it exists a lot" — historical-violation noise is opt-in.

2. Append helper — `tools/hygiene/append-tick-history-row.sh`

Wraps `cat >> file` (the canonical chronological-tail-append pattern) so the bug shape can't occur via this entrypoint. Validates new row's timestamp >= latest existing.

3. CI workflow — `gate.yml` job `lint-tick-history-order`

Wires the check into CI so violations fail fast at PR-open time.

Why this is structural, not just another rule

Per Otto-339 anywhere-means-anywhere applied to discipline-enforcement: enforce at the layer that catches all paths (CI gate at commit time), not at the input-tool layer (which would require every agent's vigilance and miss future tools / future agents).

Adding a rule in CLAUDE.md or a memory file would be Tier-1 (language layer) — Aaron's question was about structural prevention, which means moving the discipline to a mechanism layer.

Self-tests

  • `shellcheck --severity=style tools/hygiene/*.sh`: pass
  • `actionlint .github/workflows/gate.yml`: pass
  • check script default mode: clean 2-line output ("OK: last row IS latest timestamp...")
  • check script `--strict` mode: 32-line full detail
  • append helper happy path: works
  • append helper rejects backwards timestamp: works (exit 1 with helpful diagnostic)

What this DOES NOT do

  • Does NOT auto-reorder rows. The fix is the committer's responsibility (revert + re-append correctly). Auto-reordering would silently rewrite history; this check is intentionally advisory-with-teeth.
  • Does NOT validate row content beyond the timestamp prefix. Markdownlint and other lints handle table structure.
  • Does NOT prevent the bug at the Edit tool layer (impossible — that's a tool default). It catches the bug at commit/push time.

Test plan

  • Self-tests above pass locally
  • CI lint-tick-history-order job runs and passes on this PR
  • Existing CI lints continue to pass

🤖 Generated with Claude Code

…g — Aaron 2026-04-26 ask

Aaron 2026-04-26: *"anything we can do to prevent it in the first place?"* after I caught the row-ordering bug yet again this tick. The bug shape: Edit tool's old_string=existing-line pattern tends to insert NEW row BEFORE the matched line, producing reverse-chronological order in `docs/hygiene-history/loop-tick-history.md`.

This commit ships three additive layers:

1. `tools/hygiene/check-tick-history-order.sh` — CI gate that validates the LAST row in the file is the LATEST timestamp. This catches the specific bug shape (new row inserted before previous last row) without requiring history-rewrite (Otto-229 forbids editing prior rows). Two-tier:
   - Default: quiet on historical strict-order violations; only fails on the last-row check
   - `--strict`: reports historical violations too (advisory, for human spelunking)

2. `tools/hygiene/append-tick-history-row.sh` — helper that wraps `cat >> file` (the canonical chronological-tail-append pattern) so the bug shape can't occur via this entrypoint. Validates new row's timestamp >= latest existing.

3. `.github/workflows/gate.yml` `lint-tick-history-order` job — wires the check into CI so violations fail fast at PR-open time, not at the "I caught it again" moment.

Self-tests:
- shellcheck --severity=style: pass
- actionlint on gate.yml: pass
- check script default mode: clean 2-line output
- check script --strict mode: 32-line detail (3 historical violations + bug-pattern absent)
- append helper happy path: works
- append helper rejects backwards timestamp: works

Aaron's specific guidance ("we might should allow this one override if it exists a lot") implemented as the default-quiet posture: noisy historical-violation reporting is opt-in via --strict, since Otto-229 forbids the only way to fix history.

This is Otto-339 anywhere-means-anywhere applied to discipline-enforcement: enforce at the layer that catches all paths (CI gate at commit time), not at the input-tool layer (which would require every agent's vigilance and miss future tools).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@AceHack AceHack enabled auto-merge (squash) April 26, 2026 02:42
Copilot AI review requested due to automatic review settings April 26, 2026 02:42
@AceHack AceHack merged commit 6986c59 into main Apr 26, 2026
17 checks passed
@AceHack AceHack deleted the feat/tick-history-monotonic-order-check-prevent-row-ordering-bug branch April 26, 2026 02:44
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de361d1e33

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +57 to +61
LATEST_TS=$(
grep -oE '^\| [0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z' "$TICK_FILE" \
| sed 's/^| //' \
| sort \
| tail -1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle empty timestamp history before computing latest row

The helper exits before appending when loop-tick-history.md contains zero ISO timestamp rows (for example, a freshly bootstrapped file or a post-archive reset with only headers). With set -euo pipefail, the grep in the LATEST_TS pipeline returns status 1 on no matches, which aborts the script before cat >> runs, so the tool cannot append the first valid row at all.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds structural safeguards to prevent the recurring tick-history row-ordering regression by introducing a dedicated order-check script, an append helper, and wiring the check into CI.

Changes:

  • Add tools/hygiene/check-tick-history-order.sh to verify the last tick-history row is the latest timestamp (with optional --strict advisory reporting).
  • Add tools/hygiene/append-tick-history-row.sh helper to safely append new tick-history rows while enforcing non-decreasing timestamps.
  • Add a new lint-tick-history-order job in .github/workflows/gate.yml to run the check in CI.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
tools/hygiene/check-tick-history-order.sh New validator script for tick-history ordering with a CI-gating “last row is latest” check.
tools/hygiene/append-tick-history-row.sh New helper script to append tick-history rows while validating timestamp monotonicity.
.github/workflows/gate.yml Adds a CI job to run the new tick-history order validator.

Comment on lines +75 to +79
# Append using heredoc (the whole point of this script — bash
# heredoc is the canonical chronological-tail-append pattern)
cat >> "$TICK_FILE" << EOF
$ROW
EOF
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 (security): The heredoc uses an unquoted delimiter (<< EOF), so bash will perform expansion (incl. command substitution) on the here-doc body. If the provided row text ever contains $(...) / backticks / $var, it could execute or mutate content unexpectedly. Prefer appending via printf '%s\n' "$ROW" >> "$TICK_FILE", or use a single-quoted heredoc delimiter (<<'EOF') and emit the row without additional expansion.

Suggested change
# Append using heredoc (the whole point of this script — bash
# heredoc is the canonical chronological-tail-append pattern)
cat >> "$TICK_FILE" << EOF
$ROW
EOF
# Append the row literally so caller-provided content is treated
# as data, not shell syntax.
printf '%s\n' "$ROW" >> "$TICK_FILE"

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +25
# What this checks:
# - Every row matching the ISO-8601 timestamp prefix
# `| YYYY-MM-DDTHH:MM:SSZ (...)` is extracted in file order
# - Timestamps must be non-decreasing (allows duplicates from
# close ticks; forbids out-of-order)
# - Reports first violation with surrounding context and
# exits non-zero on failure
#
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 (documentation): The file header and mid-file comments claim this script "validates" full non-decreasing order and "exits non-zero on failure", but the implementation only gates CI on the PRIMARY check (last row is the latest timestamp). Strict-order violations are advisory (and only printed when --strict is set). Please align the comments at the top and around the two-tier section with the actual behavior so readers don’t assume CI enforces full ordering.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +53
# --strict: also report (advisory) historical strict-order violations
# anywhere in the file. Default is quiet because Otto-229 forbids
# editing prior rows so historical disorder cannot be repaired —
# reporting it on every CI run is noise. Aaron 2026-04-26: "we
# might should allow this one override if it exists a lot."
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 (documentation/behavior mismatch): This comment says the default mode is "quiet" on historical strict-order violations to avoid CI noise, but later the script still prints an advisory summary when violations > 0 even without --strict. Either change the behavior to be fully quiet in default mode, or adjust this comment to match the actual output contract.

Suggested change
# --strict: also report (advisory) historical strict-order violations
# anywhere in the file. Default is quiet because Otto-229 forbids
# editing prior rows so historical disorder cannot be repaired —
# reporting it on every CI run is noise. Aaron 2026-04-26: "we
# might should allow this one override if it exists a lot."
# --strict: report (advisory) historical strict-order violations
# anywhere in the file with full detail. Default mode does not
# fail on that historical disorder, but it may still print a brief
# advisory summary when such violations exist. Otto-229 forbids
# editing prior rows, so repeated historical findings are mostly
# noise unless explicitly requested. Aaron 2026-04-26: "we might
# should allow this one override if it exists a lot."

Copilot uses AI. Check for mistakes.
Comment on lines +328 to +333
# reverse-chronological order. Aaron 2026-04-26 asked for
# structural prevention after I caught this bug at least
# three times across recent ticks. The check is the
# "fail-fast at commit/push time" mechanism that doesn't
# rely on each agent's vigilance — Otto-339 anywhere-means-
# anywhere applied to discipline-enforcement: enforce at
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 (codebase convention): This workflow comment includes direct contributor name attribution ("Aaron …"). Repo operational rule is to avoid names on current-state surfaces like workflows/scripts and use role references (e.g., "human maintainer"), reserving names for the explicitly enumerated history surfaces (docs/AGENT-BEST-PRACTICES.md:284-347). Please rewrite these attributions here (and in the new hygiene scripts) to role-refs.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants