Skip to content

onion-time-pre-script: land the output-stability fixes on master - #79

Closed
assisted-by-ai wants to merge 3 commits into
masterfrom
ai-onion-time-output-stability
Closed

onion-time-pre-script: land the output-stability fixes on master#79
assisted-by-ai wants to merge 3 commits into
masterfrom
ai-onion-time-output-stability

Conversation

@assisted-by-ai

@assisted-by-ai assisted-by-ai commented Jul 31, 2026

Copy link
Copy Markdown

master is red on dist-ai-tests, 4 failures:

test_output_stability.TestBootstrapOutputStability.test_counter_is_not_present_in_output
test_output_stability.TestBootstrapOutputStability.test_retry_counter_tick_does_not_change_output
test_output_stability.TestBootstrapOutputStability.test_neighbouring_fields_survive_redaction
test_output_stability.TestExitHandler.test_unset_exit_code_becomes_one

Those are the regression tests for the onion-time-pre-script busy-wait. dist-ai master carries the tests; helper-scripts master lacks the fixes, so the tests fail here rather than on the branch that introduced them -- the usual cross-repo ordering problem where the suite and the component land separately.

The bug the tests cover

sdwdate's preparation loop lengthens its retry wait after each failed run, but resets to the minimum whenever the output differs from the previous run. Tor's bootstrap line carries COUNT=, a retry counter incremented on every failed connection attempt, so on a Gateway with no network the output never repeated, the wait never advanced, and the loop respawned six Tor control-port helpers per second indefinitely.

Contents

Three commits cherry-picked unchanged from ai (PR #78), authored by other sessions -- this PR only lands them on master ahead of that branch:

commit covers
dc73dc42 rate limit anondate-set requests test_unset_exit_code_becomes_one (carries the exit_handler fix -- it ran its "No exit code set yet. Setting to 1." string as a command and never set exit_code)
327bd174 brace variable references, run shell tests first included for a clean apply; its own message notes R-010 is knowingly left failing on this file
da46a506 redact Tor's retry counter from the output the three TestBootstrapOutputStability failures

Only the printed copy is redacted -- ${tor_bootstrap_status} keeps the counter, so the REASON=TIMEOUT match is unaffected and systemcheck still shows the full line.

Note

PR #78 contains these same commits among 48 others and currently reports mergeable_state: unstable. If #78 lands first this PR becomes redundant and should be closed unmerged; it exists so master can go green without waiting on the rest of that branch.

Generated with assistance from Claude Code.

Summary by CodeRabbit

  • Bug Fixes

    • Added rate limiting for anonymous date requests, preventing repeated requests within a 60-second interval.
    • Improved handling of request timestamps, exit statuses, and shell variable expansion.
    • Stabilized Tor bootstrap messages by hiding changing retry-count values while preserving status information.
  • Tests

    • Added regression coverage for request throttling, timestamp edge cases, workstation behavior, and cleanup.
    • Updated the test runner to execute the new checks and apply more reliable error handling.

claude added 3 commits July 31, 2026 13:45
- anondate_use created /run/sdwdate/request_anondate-set on every call.
- sdwdate's preparation loop calls this script about once per second while
  Tor is unreachable, and the consumer
  (sdwdate/usr/libexec/sdwdate/sdwdate-start-anondate-set-file-watcher) is a
  bare inotify read loop with no limit of its own, so every request became a
  full anondate-set run.
- Suppress a request when the previous one is under 60s old. Nothing
  anondate-set consumes moves faster: it derives the time from the Tor
  consensus (hourly, valid 3h) or the Tor certificate lifetime.
- Timestamp kept in its own file; the consumer deletes the request file once
  serviced, so the request file's mtime cannot carry the state.
- Stamp written before the request file, so a death between the two leaves
  the limit armed rather than unarmed.
- First request after boot is unaffected (/run is empty, no stamp).
- A stamp dated in the future means the clock moved backwards, which is
  expected because anondate-set and sdwdate both set the clock. Fail open.

Regression test: onion-time-pre-script-testscript, wired into ./run-tests.
Verified 11 pass / 0 fail / 0 skip on the fix, and 2 fail on the pre-fix
code (including the request-not-recreated assertion).

Drive-by, to leave shellcheck clean on the touched file:
- exit_handler ran its "No exit code set yet. Setting to 1." string as a
  command and never set exit_code (SC2288).
- output_cmd msg="$@" -> "$*" (SC2124).
- $FUNCNAME -> ${FUNCNAME[0]} (SC2128).

ci/lint-install.sh: install safe-rm, used by the shell tests in ./run-tests.

Co-Authored-By: Claude <noreply@anthropic.com>
- run-tests: move onion-time-pre-script-testscript ahead of the python
  suites. run-tests is errexit, and the stdisplay suite is currently red in
  CI, so the shell test was never reached -- wired in but not running.
- onion-time-pre-script: brace all 77 SC2250 sites (R-020), drop the
  blank-line separator (R-042).
- run-tests, ci/lint-install.sh: R-010 strict-mode block; brace $PWD.

R-010 on onion-time-pre-script is knowingly left failing. It reads 18
variables it never assigns (most from the sourced tor_bootstrap_check.bsh)
and relies on unset-expands-to-empty, so nounset needs its own audit and a
test round on the time-sync path. The file failed R-010 the same way before
this branch touched it.

Regression suite still 11 pass / 0 fail / 0 skip after the rewrite.

Co-Authored-By: Claude <noreply@anthropic.com>
sdwdate's preparation loop lengthens its retry wait after each failed run
of this script, but drops back to the minimum whenever the output differs
from the previous run. Tor's bootstrap line carries COUNT=, a retry
counter incremented on every failed connection attempt, so on a Gateway
with no network the output never repeated, the wait never advanced, and
the loop respawned six Tor control port helpers per second indefinitely:
https://forums.whonix.org/t/sys-whonix-has-extreme-cpu-usage-before-an-internet-connection/23450
QubesOS/qubes-issues#11013

Only the printed copy is redacted. ${tor_bootstrap_status} keeps the
counter, so the REASON=TIMEOUT match below is unaffected, and systemcheck
still shows the full line in the eleven places it displays it -- which is
where this script's own log messages already tell the user to look.

COUNT= is the only field that moves on every run. The consensus
valid-after/valid-until timestamps only change when a new consensus
arrives, and timesanitycheck emits a static message.

Redacting is a denylist over a format we do not own, so sdwdate does not
rely on it being complete: it also floors its retry interval, which
bounds the cost regardless of what this script prints.

Regression tests in dist-ai (onion-time-pre-script-tests). They pin BOTH
directions -- a counter tick must not change the output, and a
PROGRESS/REASON transition must still change it -- because a redaction
that silenced real transitions would trade a CPU bug for a
responsiveness bug. 11 pass, 0 fail, 0 skip; against the unredacted
script 4 fail.

Generated with assistance from Claude Code.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The helper script now rate-limits anondate requests with timestamp files, improves shell quoting and diagnostics, redacts variable Tor retry counts, and adds regression coverage. CI installs safe-rm, enables explicit Bash options, and runs the new test early.

Changes

Anondate and shell reliability

Layer / File(s) Summary
Helper script hardening and diagnostics
usr/libexec/helper-scripts/onion-time-pre-script
The script quotes variable expansions, fixes unset exit-code diagnostics, and redacts numeric COUNT= values from displayed Tor bootstrap status.
Anondate request rate limiting
usr/libexec/helper-scripts/onion-time-pre-script
anondate_use uses a 60-second stamp-file interval, treats missing or future stamps as stale, and returns code 2 when throttled.
Regression coverage and CI execution
onion-time-pre-script-testscript, run-tests, ci/lint-install.sh
The regression script tests Gateway and Workstation behavior. CI runs it before other tests, enables explicit Bash options, installs safe-rm, and updates the workspace fallback.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TestScript
  participant anondate_use
  participant RequestFiles
  TestScript->>anondate_use: Invoke anondate_use
  anondate_use->>RequestFiles: Read timestamp stamp
  anondate_use->>RequestFiles: Write stamp before request file
  anondate_use-->>TestScript: Return request status
Loading

Possibly related PRs

Suggested reviewers: claude, adrelanos, arraybolt3

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main output-stability fixes and the target branch, matching the pull request objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ai-onion-time-output-stability

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@usr/libexec/helper-scripts/onion-time-pre-script`:
- Around line 397-411: Update the anondate request path around
anondate_request_rate_limited to acquire a shared atomic lock before checking
the rate-limit stamp. After the lock is acquired, re-check the stamp and exit
with the existing rate-limit result when it is fresh; only then create
request_anondate-set.stamp and request_anondate-set while holding the lock,
releasing it afterward.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 68eec248-81f7-412b-b26d-0d2f6df2439d

📥 Commits

Reviewing files that changed from the base of the PR and between 673ff63 and 0074f26.

📒 Files selected for processing (4)
  • ci/lint-install.sh
  • onion-time-pre-script-testscript
  • run-tests
  • usr/libexec/helper-scripts/onion-time-pre-script

Comment on lines +397 to +411
if anondate_request_rate_limited; then
output_cmd "anondate_use: Skipping 'anondate-set' request, the previous request is less than ${anondate_request_min_interval_seconds} seconds old, ok."
exit_code=2
exit "${exit_code}"
fi

## https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
## https://www.whonix.org/wiki/Dev/TimeSync#Fixing_Time_based_on_Tor_Consensus
## /usr/libexec/sdwdate/sdwdate-start-anondate-set-file-watcher
output_cmd "anondate_use: Running 'anondate-set' (by creating file '$anondate_state_folder/request_anondate-set')..."
touch "$anondate_state_folder/request_anondate-set"
output_cmd "anondate_use: Running 'anondate-set' (by creating file '${anondate_state_folder}/request_anondate-set')..."

## Stamp before request: if this script dies between the two, the next call
## is still rate limited. The reverse order would leave the limit unarmed.
touch "${anondate_state_folder}/request_anondate-set.stamp"
touch "${anondate_state_folder}/request_anondate-set"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make the rate-limit claim atomic.

Two concurrent calls can both observe no fresh stamp at Line 397. Both calls can then create a request at Lines 410-411. This bypasses the one-request-per-60-seconds requirement and can start multiple anondate-set runs.

Use a shared atomic lock, then re-check the stamp after acquiring the lock before creating either file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@usr/libexec/helper-scripts/onion-time-pre-script` around lines 397 - 411,
Update the anondate request path around anondate_request_rate_limited to acquire
a shared atomic lock before checking the rate-limit stamp. After the lock is
acquired, re-check the stamp and exit with the existing rate-limit result when
it is fresh; only then create request_anondate-set.stamp and
request_anondate-set while holding the lock, releasing it afterward.

@assisted-by-ai

Copy link
Copy Markdown
Author

Closing: this branch cherry-picked commits that already live on the ai branch (PR #78). Per the one-ai-branch rule these output-stability fixes should land via that PR, not a separate branch. The three commits (dc73dc4, 327bd17, da46a50) are ancestors of ai, so nothing is lost -- they reach master when #78 merges. Stray branch deleted.

@assisted-by-ai
assisted-by-ai deleted the ai-onion-time-output-stability branch August 1, 2026 09:19
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