Skip to content

fix(compose): forward the observer's cranker and cleanup settings - #857

Merged
vilenarios merged 2 commits into
developfrom
fix/forward-observer-cranker-env
Aug 13, 2026
Merged

fix(compose): forward the observer's cranker and cleanup settings#857
vilenarios merged 2 commits into
developfrom
fix/forward-observer-cranker-env

Conversation

@vilenarios

Copy link
Copy Markdown
Contributor

The observer reads ten settings that docker-compose.yaml never passed into its container, so setting any of them in .env silently did nothing:

CRANK_BATCH_SIZE            CLEANUP_BATCH_SIZE
CRANK_CLOSE_EPOCHS          CLEANUP_FAILURE_THRESHOLD
CRANK_EPOCH_RETENTION       MAX_CLEANUP_TXS_PER_CYCLE
CRANK_WARN_BALANCE_SOL      ALT_RECLAIM_SCAN_LIMIT
CRANK_CRITICAL_BALANCE_SOL  OBSERVED_GATEWAY_HOSTS

Same gap #842 fixed for CRANK_POLL_INTERVAL_MS / CLEANUP_MIN_INTERVAL_MS and #846 fixed for CLEANUP_TO_RETURNED_TXS_PER_CYCLE — the rest of the family was never wired.

What prompted it

CRANK_EPOCH_RETENTION governs how many epochs survive before close_epoch reclaims their rent, and closing an epoch also closes its Observation PDAs — which is what removes per-observer report detail from chain.

Measured on mainnet: only the two most recent epochs still had Observation accounts (13 for epoch 510, 9 for 511, nothing older), while the Epoch PDAs for 506–509 survived holding only aggregate observationsSubmitted counts. An operator wanting more history had no way to ask for it.

ALT_RECLAIM_SCAN_LIMIT matters for the same reason from the other direction: reclaiming a prescribe Address Lookup Table removes the observer index for its epoch, so getEpoch can no longer enumerate that epoch's reports even while the underlying accounts are alive.

Worth noting for anyone reading this later: close_observation and close_epoch are permissionless, so the effective retention across the network is set by the most aggressive cranker running, not by any single operator. This PR makes the setting reachable; it doesn't make it authoritative.

Regression check

This one had a real failure mode worth verifying rather than assuming. parsePositiveIntEnv does Number.parseInt(raw) and throws on a non-positive integer — so if ${VAR:-} rendered an empty string that reached it, the observer would fail to start on every deployment that doesn't set these.

Verified against the running observer image (15e285b0):

  • env.varOrDefault returns the supplied default for both empty and whitespace-only values, so a blank never reaches the numeric parsers.
  • Loading dist/config.js with all ten set to '' yields every documented default unchanged:
CRANK_BATCH_SIZE            '' -> 15      CLEANUP_BATCH_SIZE         '' -> 15
CRANK_CLOSE_EPOCHS          '' -> true    CLEANUP_FAILURE_THRESHOLD  '' -> 30
CRANK_EPOCH_RETENTION       '' -> 7       MAX_CLEANUP_TXS_PER_CYCLE  '' -> 50
CRANK_WARN_BALANCE_SOL      '' -> 0.3     ALT_RECLAIM_SCAN_LIMIT     '' -> 200
CRANK_CRITICAL_BALANCE_SOL  '' -> 0.1

Also confirmed docker compose config renders the values through when set (CRANK_EPOCH_RETENTION: "14", ALT_RECLAIM_SCAN_LIMIT: "0") and empty when unset.

Scope

Seven observer settings remain unforwarded — PORT (intentional; compose maps OBSERVER_PORT), ARNS_NAMES, ALWAYS_SAVE_REPORTS, REFERENCE_GATEWAY_HOST, BLOCK_OFFSET_MAPPING_ENABLED, TX_PATH_PARSING_ENABLED, ENABLE_OPENAPI_VALIDATION. Those are observer-internal/dev knobs rather than operational cranker settings, so I left them out to keep this coherent. Happy to add them if preferred.

Documented in docs/envs.md per the compose/envs.md sync rule.

The observer reads ten settings that docker-compose never passed into its
container, so setting any of them in .env silently did nothing:

  CRANK_BATCH_SIZE            CLEANUP_BATCH_SIZE
  CRANK_CLOSE_EPOCHS          CLEANUP_FAILURE_THRESHOLD
  CRANK_EPOCH_RETENTION       MAX_CLEANUP_TXS_PER_CYCLE
  CRANK_WARN_BALANCE_SOL      ALT_RECLAIM_SCAN_LIMIT
  CRANK_CRITICAL_BALANCE_SOL  OBSERVED_GATEWAY_HOSTS

This is the same gap #842 fixed for CRANK_POLL_INTERVAL_MS /
CLEANUP_MIN_INTERVAL_MS and #846 fixed for CLEANUP_TO_RETURNED_TXS_PER_CYCLE;
the rest of the family was never wired.

CRANK_EPOCH_RETENTION is the one that prompted this. It governs how many
epochs of on-chain history survive before close_epoch reclaims their rent,
and closing an epoch also closes its Observation PDAs - which is what removes
per-observer report detail from chain. On mainnet only the two most recent
epochs still had Observation accounts, so an operator wanting more history had
no way to ask for it. ALT_RECLAIM_SCAN_LIMIT matters for the same reason:
reclaiming a prescribe Address Lookup Table removes the observer index for its
epoch, so getEpoch can no longer enumerate that epoch's reports.

Verified against the running observer image (15e285b0) that an empty value is
a true no-op rather than a parse error: env.varOrDefault returns the default
for both empty and whitespace-only values, and loading dist/config.js with all
ten set to '' yields every documented default unchanged (retention 7, batch 15,
close true, scan limit 200, cleanup txs 50, thresholds 30/0.3/0.1). That
mattered because parsePositiveIntEnv throws on a non-positive integer, so a
blank reaching it would have failed observer startup for every deployment.

Documented in docs/envs.md per the compose/envs.md sync rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f0ee0b39-50d2-49d2-af92-f2c9dd4b0427

📥 Commits

Reviewing files that changed from the base of the PR and between 04012d0 and 62afa1e.

📒 Files selected for processing (1)
  • docs/envs.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/envs.md

📝 Walkthrough

Walkthrough

The observer service now receives cranking, cleanup, balance-alert, ALT-reclamation, and gateway-host settings from environment variables. Documentation and the changelog describe these settings and empty-value default behavior.

Changes

Observer configuration

Layer / File(s) Summary
Forward and document observer settings
docker-compose.yaml, docs/envs.md, CHANGELOG.md
Docker Compose forwards observer configuration variables. The environment documentation lists the variables. The changelog records the update and preservation of existing defaults for empty values.

Estimated code review effort: 2 (Simple) | ~10 minutes

Mergeability Score: 🔵 Low · up to 62afa

The change makes observer cranker and cleanup settings configurable through Compose, including retention and host-list settings. The PR is mergeable with owner awareness because whitespace in the comma-separated observed-host setting is not clearly defined and could cause a configured host to be skipped.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: forwarding observer cranker and cleanup settings through Docker Compose.
Description check ✅ Passed The description directly explains the missing environment-variable forwarding, documented settings, validation, and intentional scope.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/forward-observer-cranker-env

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
Contributor

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 `@docs/envs.md`:
- Line 189: Update the OBSERVED_GATEWAY_HOSTS documentation and observer
handling to define whitespace behavior: either require and document the no-space
format host-a,host-b, or trim each parsed host entry. Add a regression check
covering the input host-a, host-b and ensure both hosts are recognized.
🪄 Autofix

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 364a246c-2d12-4812-a0c4-6d92c05f5054

📥 Commits

Reviewing files that changed from the base of the PR and between d330e70 and 04012d0.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • docker-compose.yaml
  • docs/envs.md

Comment thread docs/envs.md Outdated
| CLEANUP_FAILURE_THRESHOLD | Number | 30 | Consecutive-failure count at which a gateway becomes eligible for `prune_gateway`. Mirrors the on-chain `EpochSettings.max_consecutive_failures`. |
| MAX_CLEANUP_TXS_PER_CYCLE | Number | 50 | Hard cap on cleanup transactions submitted per cleanup cycle, shared across all cleanup phases. Bounds gas spend if discovery returns thousands of stale accounts. |
| ALT_RECLAIM_SCAN_LIMIT | Number | 200 | Recent signatures scanned per pass when reclaiming rent from single-use prescribe Address Lookup Tables (~0.0126 SOL each). Set `0` to disable reclamation. Reclaiming a prescribe ALT removes the observer index for its epoch, so `getEpoch` can no longer enumerate that epoch's reports even while the underlying accounts live. |
| OBSERVED_GATEWAY_HOSTS | String | (all registered gateways) | Comma-separated list restricting which gateway hosts this observer assesses. Empty observes every gateway in the registry. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- documentation context ---'
sed -n '175,192p' docs/envs.md

printf '%s\n' '--- observer parser ---'
curl -fsSL \
  https://raw.githubusercontent.com/ar-io/ar-io-observer/15e285b0a539dfb732ab70e13ab062c02c9906d0/src/config.ts \
  | nl -ba | sed -n '1,220p'

Repository: ar-io/ar-io-node

Length of output: 6493


🏁 Script executed:

#!/bin/bash
set -eu

curl -fsSL \
  https://raw.githubusercontent.com/ar-io/ar-io-observer/15e285b0a539dfb732ab70e13ab062c02c9906d0/src/config.ts \
  | sed -n '1,240p'

Repository: ar-io/ar-io-node

Length of output: 7739


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- OBSERVED_GATEWAY_HOSTS references ---'
rg -n -C 4 'OBSERVED_GATEWAY_HOSTS|observedGatewayHosts|observed-gateway-hosts' . \
  -g '!node_modules' -g '!dist' -g '!build'

Repository: ar-io/ar-io-node

Length of output: 4748


🏁 Script executed:

node - <<'JS'
const values = ['host-a,host-b', 'host-a, host-b', ''];
for (const value of values) {
  const parsed = value.split(',').filter((host) => host.length > 0);
  console.log(JSON.stringify({ value, parsed }));
}
JS

Repository: ar-io/ar-io-node

Length of output: 291


Define whitespace handling for OBSERVED_GATEWAY_HOSTS.

The observer does not trim entries. Document a no-space format, such as host-a,host-b, or trim entries in the observer. Add a regression check for host-a, host-b.

🤖 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 `@docs/envs.md` at line 189, Update the OBSERVED_GATEWAY_HOSTS documentation
and observer handling to define whitespace behavior: either require and document
the no-space format host-a,host-b, or trim each parsed host entry. Add a
regression check covering the input host-a, host-b and ensure both hosts are
recognized.

Source: MCP tools

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.79%. Comparing base (dae464e) to head (62afa1e).
⚠️ Report is 21 commits behind head on develop.

Additional details and impacted files
@@           Coverage Diff            @@
##           develop     #857   +/-   ##
========================================
  Coverage    79.78%   79.79%           
========================================
  Files          141      141           
  Lines        54342    54342           
  Branches      4171     4169    -2     
========================================
+ Hits         43359    43364    +5     
+ Misses       10931    10925    -6     
- Partials        52       53    +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The observer splits this var on ',' without trimming, so 'a.com, b.com'
yields a second entry of ' b.com' that matches no gateway and silently
narrows the observation set.

Verified against the 15e285b0 image:
  'host-a, host-b' -> ["host-a"," host-b"]
  'host-a,host-b'  -> ["host-a","host-b"]

Documenting the constraint here since the parsing lives in ar-io-observer.
A follow-up there adds the trim (REFERENCE_GATEWAY_HOSTS, defined directly
above it, already does), after which this note can be dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vilenarios

Copy link
Copy Markdown
Contributor Author

Handled the CodeRabbit finding — it was correct. Verified against the 15e285b0 observer image:

'host-a, host-b'  ->  ["host-a"," host-b"]   <- leading space, matches nothing
'host-a,host-b'   ->  ["host-a","host-b"]

Worth noting it fails silently: the malformed entry just never matches a gateway, so the observation set narrows with no error.

Addressed both halves of the suggestion rather than picking one:

  1. Here (62afa1e): documented the no-space constraint, since the parsing lives in ar-io-observer and can't be fixed from this repo.
  2. Root cause — fix(config): trim entries in comma-separated host and name lists ar-io-observer#123: adds the trim. REFERENCE_GATEWAY_HOSTS, defined directly above OBSERVED_GATEWAY_HOSTS in the same file, already trims; the other two list vars diverged from it. ARNS_NAMES had the identical bug and is fixed too. 375 tests passing, 6 new covering the reported input.

Once #123 ships, the doc note here can be dropped.

Also flagging why this surfaced now: OBSERVED_GATEWAY_HOSTS was never forwarded into the observer container before this PR, so the footgun was unreachable via compose and is about to become reachable.

@vilenarios
vilenarios merged commit bd9dd8d into develop Aug 13, 2026
5 of 7 checks passed
@vilenarios
vilenarios deleted the fix/forward-observer-cranker-env branch August 13, 2026 12:53
pull Bot pushed a commit to AKJUS/ar-io-observer that referenced this pull request Aug 13, 2026
OBSERVED_GATEWAY_HOSTS and ARNS_NAMES split on ',' without trimming, so the
natural way to write a list breaks them:

  'a.com, b.com' -> ['a.com', ' b.com']

The ' b.com' entry matches no gateway, so it fails SILENTLY - the host is
simply never found and the observation set narrows, with no error to explain
why. ARNS_NAMES has the same shape.

REFERENCE_GATEWAY_HOSTS, defined directly above these two, already does
.map((h) => h.trim()). This aligns the other two with that behavior rather
than introducing a new convention.

Extracted parseCommaSeparatedList so the parsing is unit-testable: config.ts
runs its parsing at module load, so the values themselves can only be
exercised by reimporting the module under mutated process.env.

Behavior is unchanged for well-formed input: already-trimmed lists parse
identically, and an unset var still yields [] (config passes '' when neither
env nor CLI arg is set, and an empty list means 'no restriction'). Verified
against the built dist:

  'host-a, host-b' -> ["host-a","host-b"]
  unset            -> []

Found by CodeRabbit on ar-io/ar-io-node#857, which forwards
OBSERVED_GATEWAY_HOSTS into the observer container for the first time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
arielmelendez pushed a commit that referenced this pull request Aug 13, 2026
Adds the background-verification withholding fix (#855) and moves the
observer cranker env forwarding (#857) out of [Unreleased] into
[Release 82] — both ship in r82, since the release images are built from
the current develop tip. Also updates the release date to the actual
release day.
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.

1 participant