Skip to content

telemetry: materialize dropped-event counter children at 0#927

Open
philosaether wants to merge 3 commits into
mainfrom
phil/telemetry-dropped-zero-init
Open

telemetry: materialize dropped-event counter children at 0#927
philosaether wants to merge 3 commits into
mainfrom
phil/telemetry-dropped-zero-init

Conversation

@philosaether

@philosaether philosaether commented Jul 22, 2026

Copy link
Copy Markdown

What

Pre-create the telemetry_events_dropped counter's (namespace, reason) child series at 0 when the telemetry emitter starts, for each reason the emitter can emit ({prefix}buffer_full, {prefix}send_failed).

Why

A labeled Prometheus counter exports no time series until its first labels(...) call. So telemetry_events_dropped was invisible in Prometheus/Grafana until an event was actually dropped — which means:

  • You can't alert on or graph a metric that doesn't exist yet.
  • "No drops" was indistinguishable from "metric missing / scrape broken".

Materializing the children at 0 on startup keeps the metric always present, so a missing series now signals a real problem rather than a healthy system.

Changes

  • src/telemetry/prometheus/metrics.py: add initialize_telemetry_dropped_metrics(reasons=...), which pre-touches each (namespace, reason) child (namespace auto-injected by NamespacedCounter, matching existing record_telemetry_event_dropped usage).
  • src/telemetry/emitter.py: call it on emitter start with the emitter's own prefixed reasons.

Testing

Verified in a sandbox: the counter's zero-valued children appear on the metrics endpoint before any drop occurs, and increment correctly when drops happen.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Prometheus telemetry now pre-initializes dropped-event metric series (buffer-full and send-failure reasons) so they’re visible before any drops occur.
    • Metric series initialization is more resilient, avoiding startup failures when metrics materialization encounters errors.
  • New Features
    • Added a Prometheus gauge for the reconciliation “pending embeddings” backlog, updated after sweep completion across reconciliation modes.
    • Extended startup bounded-metric initialization for improved early visibility in both API and deriver processes (including embeddings-pending state).
  • Tests
    • Added a comprehensive test suite covering metric zero-initialization behavior and drift-guarded label sets.

A labeled Prometheus counter exports no series until its first labels()
call, so telemetry_events_dropped stayed invisible until an event was
actually dropped — impossible to alert on or graph, and "no drops" was
indistinguishable from "metric missing / scrape broken".

Pre-create the (namespace, reason) children at 0 on emitter start, for
each reason the emitter can emit, so the metric is always present.

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

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Prometheus metrics now pre-materialize bounded label series during API, deriver, and emitter startup. Reconciliation reports pending message embeddings through a gauge, with tests covering valid domains, process isolation, dropped counters, and disabled metrics.

Changes

Telemetry metrics initialization

Layer / File(s) Summary
Metric domains and zero-initialization
src/telemetry/events/__init__.py, src/telemetry/prometheus/metrics.py
Defines bounded event and deriver label domains, adds fail-soft zero-initialization helpers, and introduces the pending embeddings gauge.
Application and emitter startup initialization
src/main.py, src/deriver/__main__.py, src/telemetry/emitter.py
Initializes bounded metrics for API and deriver processes and pre-creates buffer-full and send-failure drop labels.
Pending embeddings backlog reporting
src/reconciler/sync_vectors.py, src/telemetry/prometheus/metrics.py
Counts pending embeddings after reconciliation and updates the Prometheus gauge without failing reconciliation on errors.
Metric initialization validation
tests/telemetry/test_metric_zero_init.py
Tests bounded-label completeness, process isolation, dropped-counter backfilling, and disabled-metrics behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Poem

A rabbit counts embeddings in a telemetry burrow,
Planting metric labels before events can scurry.
Zeroes bloom gently, and failed drops stay tame,
While pending rows hop into a gauge by name.
Hop, hop—the metrics are ready!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: pre-materializing dropped-event counter children at zero.
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 phil/telemetry-dropped-zero-init

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 `@src/telemetry/prometheus/metrics.py`:
- Around line 341-347: Update initialize_telemetry_dropped_metrics to catch only
the expected Prometheus labels(reason=reason) validation exception, using the
appropriate specific Prometheus exception type. Let unexpected exceptions from
telemetry_events_dropped_counter.labels propagate instead of routing them
through _handle_metric_error, preserving the existing handling for valid label
errors.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f114054c-c059-4b97-816c-ba9cad4842e6

📥 Commits

Reviewing files that changed from the base of the PR and between 4f9a413 and 56ab5dc.

📒 Files selected for processing (2)
  • src/telemetry/emitter.py
  • src/telemetry/prometheus/metrics.py

Comment thread src/telemetry/prometheus/metrics.py Outdated
Comment on lines +341 to +347
for reason in reasons:
try:
telemetry_events_dropped_counter.labels(reason=reason)
except Exception as e:
self._handle_metric_error(
"initialize_telemetry_dropped_metrics", e
)

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate metrics.py:"
fd -a 'metrics.py$' . | sed 's#^\./##'

echo
echo "File size and relevant section:"
wc -l src/telemetry/prometheus/metrics.py
sed -n '300,370p' src/telemetry/prometheus/metrics.py

echo
echo "Search for _handle_metric_error and telemetry_events_dropped_counter definitions/usages:"
rg -n "_handle_metric_error|telemetry_events_dropped_counter|initialized_telemetry_dropped|class .*Counter|def labels" src/telemetry src 2>/dev/null | head -200

Repository: plastic-labs/honcho

Length of output: 7997


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate metrics.py:"
fd -a 'metrics.py$' . | sed 's#^\./##'

echo
echo "File size and relevant section:"
wc -l src/telemetry/prometheus/metrics.py
sed -n '300,370p' src/telemetry/prometheus/metrics.py

echo
echo "Search for _handle_metric_error and telemetry_events_dropped_counter definitions/usages:"
rg -n "_handle_metric_error|telemetry_events_dropped_counter|initialized_telemetry_dropped|class .*Counter|def labels" src/telemetry/src 2>/dev/null | head -200

Repository: plastic-labs/honcho

Length of output: 3365


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Relevant metrics.py sections:"
sed -n '1,90p' src/telemetry/prometheus/metrics.py
sed -n '130,190p' src/telemetry/prometheus/metrics.py

echo
echo "Inspect prometheus module requirements/imports:"
sed -n '1,60p' src/telemetry/prometheus/metrics.py | grep -n "from\|import\|prometheus" || true

echo
echo "Relevant test references (if any):"
sed -n '1,220p' tests/integration/test_token_metrics.py | grep -n "namespaced\|labels\|initialize_telemetry_dropped_metrics\|Prometheus" -i || true

Repository: plastic-labs/honcho

Length of output: 6370


🌐 Web query:

prometheus_client Counter.labels InvalidLabelValueError ValueError documentation

💡 Result:

In the prometheus_client library, a ValueError associated with.labels typically indicates an attempt to provide incorrect or malformed label data [1]. While there is no single error named "InvalidLabelValueError", the library raises standard ValueError exceptions with descriptive messages when label constraints are violated [1]. Common scenarios that trigger a ValueError when calling.labels include: 1. Incorrect Label Count: Providing a different number of arguments in.labels than the number of label names defined during the initialization of the metric [1]. 2. Mismatched Keyword Arguments: Providing keyword arguments that do not match the declared label names [1]. 3. Mixing Arguments and Keywords: Passing both positional arguments and keyword arguments to.labels [1]. 4. Calling Labels on an Already Labeled Metric: Attempting to call.labels on a metric instance that already has specific label values assigned (e.g., trying to chain.labels calls) [1]. 5. Missing Label Definition: Attempting to call.labels on a metric that was not initialized with any label names [1]. Additionally, if you receive an error stating that a metric is missing label values (often seen when attempting to call.inc or.set directly on a metric that requires labels), it is because you must first call.labels(...) to obtain a specific child metric instance before performing operations [2][3]. For example: Correct usage: counter = Counter('my_metric', 'help', ['method']) counter.labels(method='get').inc # Correct Incorrect usage (results in error): counter = Counter('my_metric', 'help', ['method']) counter.inc # ValueError or AttributeError: missing label values or _value attribute Always ensure that the arguments passed to.labels strictly align with the label names specified in the constructor [1].

Citations:


Catch only expected Prometheus labels(...) validation errors.

initialize_telemetry_dropped_metrics() catches all Exceptions, so unexpected failures in NamespacedCounter.labels(...) are logged as metric recording failures and startup can continue without the new reason series. Catch Prometheus label/validation errors explicitly, or let unexpected exceptions propagate when metric initialization is critical for alerting.
[stblability_and_availability]

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 344-344: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@src/telemetry/prometheus/metrics.py` around lines 341 - 347, Update
initialize_telemetry_dropped_metrics to catch only the expected Prometheus
labels(reason=reason) validation exception, using the appropriate specific
Prometheus exception type. Let unexpected exceptions from
telemetry_events_dropped_counter.labels propagate instead of routing them
through _handle_metric_error, preserving the existing handling for valid label
errors.

Sources: Coding guidelines, Linters/SAST tools

except Exception as e:
self._handle_metric_error("record_telemetry_event_dropped", e)

def initialize_telemetry_dropped_metrics(self, *, reasons: list[str]) -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nice - we should generalize this method to be initialize_counter_series (or something similar) to support initializing other counters in the future

@Rajat-Ahuja1997 Rajat-Ahuja1997 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

looks good, 1 comment

Extends #927 (which zero-inited telemetry_events_dropped) to every counter
whose label domain is bounded and known at startup, so metrics are present in
Prometheus before their first event — a missing series then signals a broken
scrape rather than "nothing happened yet".

- add initialize_bounded_metrics(instance_type) on PrometheusMetrics; call it
  per-process from main.py (api) and deriver/__main__.py (deriver).
- extract a shared _touch() helper; refactor initialize_telemetry_dropped_metrics
  onto it (that one stays per-emitter in start() — it's prefix-dependent).
- explicit ALL_EVENT_TYPES / HIGH_VOLUME_EVENT_TYPES registry in telemetry.events,
  drift-guarded by tests that walk BaseEvent subclasses.
- only VALID (task_type, token_type, component) tuples for deriver_tokens (the
  cartesian product would fabricate impossible always-0 series); only high-volume
  event types for sampled_out; high-cardinality labels (endpoint, workspace_name)
  left open.
- gauges: zero-init embed_now_tasks_in_flight + telemetry_buffer_size; add a new
  message_embeddings_pending backlog gauge, set each reconciliation cycle and
  zero-inited at deriver startup (Rajat's pending/in-flight ask).
- backfills the tests #927 shipped without.

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

@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: 4

🤖 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 `@src/telemetry/prometheus/metrics.py`:
- Around line 403-458: Replace the direct gauge initialization calls in
initialize_bounded_metrics with the class’s fail-soft _touch path, including
telemetry_buffer_size_gauge, embed_now_tasks_in_flight_gauge, and
message_embeddings_pending_gauge. Preserve initializing each gauge to zero while
ensuring any metric setup failure is handled consistently without interrupting
startup.

In `@tests/telemetry/test_metric_zero_init.py`:
- Around line 227-241: The test test_api_init_does_not_touch_deriver_counters
currently covers only API-to-deriver isolation. Add a before/after assertion
around prometheus_metrics.initialize_bounded_metrics(instance_type="deriver")
using an API-only bounded metric and its appropriate labels, asserting the
samples are unchanged so deriver initialization cannot materialize API metrics.
- Around line 95-102: Update the validation around _DERIVER_TOKEN_COMBOS to
compare the complete set of token/component pairs against the exact expected
pair set, rather than checking token and component membership independently.
Also assert the combo collection length equals the expected pair-set length so
duplicate entries are rejected, while preserving the existing coverage checks as
appropriate.
- Around line 31-43: Use unique namespaces in test_metric_zero_init.py for all
global REGISTRY assertions: replace the fixed NS value used by sample() with a
module/test-specific namespace, and introduce a separate unique namespace for
the disabled-metrics check at lines 258-269. Update the relevant metric
configuration and assertions to use these namespaces so materialized children
from other tests cannot affect results.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 605569e1-376d-4abd-a7a8-df494bba2741

📥 Commits

Reviewing files that changed from the base of the PR and between 56ab5dc and e472f8d.

📒 Files selected for processing (6)
  • src/deriver/__main__.py
  • src/main.py
  • src/reconciler/sync_vectors.py
  • src/telemetry/events/__init__.py
  • src/telemetry/prometheus/metrics.py
  • tests/telemetry/test_metric_zero_init.py

Comment thread src/telemetry/prometheus/metrics.py Outdated
Comment on lines +31 to +43
NS = "test"


@pytest.fixture
def metrics_enabled(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
monkeypatch.setattr("src.config.settings.METRICS.ENABLED", True)
monkeypatch.setattr("src.config.settings.METRICS.NAMESPACE", NS)
yield


def sample(name: str, **labels: str) -> float | None:
"""Value of a series if it exists, else None. Never materializes it."""
return REGISTRY.get_sample_value(name, {"namespace": NS, **labels})

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 -euo pipefail

echo "== candidate files =="
git ls-files | rg '(^|/)test_metric_zero_init\.py$|src/telemetry|prometheus_metrics|config/settings|telemetry' || true

echo "== test file size =="
wc -l tests/telemetry/test_metric_zero_init.py 2>/dev/null || true

echo "== relevant imports and fixtures =="
sed -n '1,80p' tests/telemetry/test_metric_zero_init.py 2>/dev/null || true

echo "== relevant body around assertions =="
sed -n '220,290p' tests/telemetry/test_metric_zero_init.py 2>/dev/null || true

echo "== search for REGISTRY and sample definitions/usages =="
rg -n "REGISTRY|def sample|initialize_bounded_metrics|telemetry_events_emitted_total|metrics_enabled" tests src 2>/dev/null || true

Repository: plastic-labs/honcho

Length of output: 12694


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Prometheus/telemetry module files =="
git ls-files | rg 'telemetry|prometheus_metrics|config/settings' | sed -n '1,200p' || true

echo "== all occurrences of initialize_bounded_metrics =="
rg -n "def initialize_bounded_metrics|initialize_bounded_metrics|METRICS|METRICS_ENABLED|ENABLED" . --glob '!node_modules' --glob '!dist' --glob '!build' 2>/dev/null | sed -n '1,240p' || true

Repository: plastic-labs/honcho

Length of output: 24934


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant Prometheus metrics source =="
sed -n '1,120p' src/telemetry/prometheus/metrics.py
echo "---"
sed -n '340,420p' src/telemetry/prometheus/metrics.py
echo "---"
sed -n '470,535p' src/telemetry/prometheus/metrics.py

echo "== test_metric_zero_init full outline/essential sections =="
sed -n '80,180p' tests/telemetry/test_metric_zero_init.py
echo "---"
sed -n '180,269p' tests/telemetry/test_metric_zero_init.py

echo "== registry cleanup/unregister patterns in test/telemetry files =="
rg -n "\b(unregister(REGISTRY)?|REGISTRY\.(unregister|clear)|get_sample_value|register\()" tests src --glob '!node_modules' 2>/dev/null | sed -n '1,220p'

Repository: plastic-labs/honcho

Length of output: 18711


🌐 Web query:

prometheus_client REGISTRY unregister get_sample_value preserves metrics get_sample_value None if not registered

💡 Result:

In the Prometheus Python client library, the behavior of REGISTRY.unregister(collector) and get_sample_value is as follows: REGISTRY.unregister(collector) The unregister(collector) method removes the specified collector from the registry, meaning its metrics will no longer be collected or reported during subsequent scrapes [1][2][3]. Unregistering a collector effectively tells the registry to forget about that source [3]. It does not, however, inherently "reset" or clear the internal state of a metric if it were to be re-registered later, as the metric instances maintain their own state independently of the registry [4][5]. To fully reset metrics, you must either create a new registry or manually clear/reset the metric objects themselves [4][5]. get_sample_value(name, labels=None) The get_sample_value function returns None when a specific metric sample cannot be found in the registry [6][7]. This occurs if: 1. The metric name is not registered [6][7]. 2. The metric is registered but has not yet had any samples recorded for the provided label set [6][7][8]. 3. The labels are passed in an incorrect format (e.g., as a list instead of a dictionary), which can cause the lookup to fail and return None even if the metric exists [6][7]. Because get_sample_value returns None when a sample is missing, it is common practice in testing to use it with a default value (e.g., REGISTRY.get_sample_value(...) or 0) to avoid errors when interacting with metrics that have not yet been initialized or updated [6][7].

Citations:


Use unique namespaces for global-registry assertions. REGISTRY retains metric children after they are materialized, so fixed "test" and "disabled_ns" labels can make later assertions reflect materialization from another test instead of this initializer. Generate a module/test-specific namespace for sample() and a separate disabled namespace for the disabled-metrics check.

📍 Affects 1 file
  • tests/telemetry/test_metric_zero_init.py#L31-L43 (this comment)
  • tests/telemetry/test_metric_zero_init.py#L258-L269
🤖 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 `@tests/telemetry/test_metric_zero_init.py` around lines 31 - 43, Use unique
namespaces in test_metric_zero_init.py for all global REGISTRY assertions:
replace the fixed NS value used by sample() with a module/test-specific
namespace, and introduce a separate unique namespace for the disabled-metrics
check at lines 258-269. Update the relevant metric configuration and assertions
to use these namespaces so materialized children from other tests cannot affect
results.

Comment thread tests/telemetry/test_metric_zero_init.py Outdated
Comment on lines +227 to +241
def test_api_init_does_not_touch_deriver_counters():
"""api-only init must not materialize or change a deriver-only counter.

Delta-based (before == after) so it's robust to prior tests having
materialized the series.
"""
labels = dict(
task_type=DeriverTaskTypes.INGESTION.value,
token_type=TokenTypes.INPUT.value,
component=DeriverComponents.PROMPT.value,
)
before = sample("deriver_tokens_processed_total", **labels)
prometheus_metrics.initialize_bounded_metrics(instance_type="api")
after = sample("deriver_tokens_processed_total", **labels)
assert before == after

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

Test deriver-to-API isolation too.

This only protects the API → deriver direction. Add the inverse before/after assertion around instance_type="deriver" using an API-only bounded series, so a deriver startup regression cannot silently materialize API metrics.

🤖 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 `@tests/telemetry/test_metric_zero_init.py` around lines 227 - 241, The test
test_api_init_does_not_touch_deriver_counters currently covers only
API-to-deriver isolation. Add a before/after assertion around
prometheus_metrics.initialize_bounded_metrics(instance_type="deriver") using an
API-only bounded metric and its appropriate labels, asserting the samples are
unchanged so deriver initialization cannot materialize API metrics.

I1: _DERIVER_TOKEN_COMBOS was factored task-independently, materializing the
impossible (ingestion, input, previous_summary) series — previous_summary is
summary-only. Make combos task-aware (_DERIVER_TOKEN_COMBOS_BY_TASK) so no
always-0 impossible series is fabricated, matching the PR's own goal. Tests
tightened to assert the ingestion/previous_summary series is absent.

I2: the three gauge .set(0) zero-inits were bare while the counter inits go
through the fail-soft _touch. Add _set_gauge_zero() so a gauge init can't
propagate an exception into process startup either.

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

@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.

🧹 Nitpick comments (1)
src/telemetry/prometheus/metrics.py (1)

379-381: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a Google-style docstring.

Document the gauge parameter in an Args: section.

Proposed fix
     def _set_gauge_zero(self, gauge: NamespacedGauge) -> None:
-        """Materialize a (namespace-only) gauge at 0. Fail-soft, like ``_touch``:
-        a startup init must never propagate an exception into process boot."""
+        """Materialize a namespace-only gauge at zero without failing startup.
+
+        Args:
+            gauge: The namespace-aware gauge to materialize.
+        """
🤖 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 `@src/telemetry/prometheus/metrics.py` around lines 379 - 381, Update the
_set_gauge_zero docstring to Google style by adding an Args: section that
documents the gauge parameter and its NamespacedGauge type, while preserving the
existing behavior description.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@src/telemetry/prometheus/metrics.py`:
- Around line 379-381: Update the _set_gauge_zero docstring to Google style by
adding an Args: section that documents the gauge parameter and its
NamespacedGauge type, while preserving the existing behavior description.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 70ebefb0-e803-4f21-a26c-4130f2563d10

📥 Commits

Reviewing files that changed from the base of the PR and between e472f8d and 313fd41.

📒 Files selected for processing (2)
  • src/telemetry/prometheus/metrics.py
  • tests/telemetry/test_metric_zero_init.py

@karaokedjodua

Copy link
Copy Markdown

Finn-loop review of 313fd41

CI: not configured (gh pr checks --required returns "no required checks reported on the 'phil/telemetry-dropped-zero-init' branch")
Mergeability: BLOCKED — mergeStateStatus=BLOCKED indicates branch protection blocks merge. Despite all individual checks passing (basedpyright, test-python, CodeQL, CodeRabbit), GitHub's merge queue reports BLOCKED, meaning required checks exist in branch-protection rules but don't match the ones running. This is a CI configuration gap, not a code defect.

Review

Summary: PR materializes Prometheus counter children at 0 on startup so metrics like telemetry_events_dropped are visible in Grafana before any event fires — "no drops" vs "metric missing" becomes distinguishable. The code also pre-initializes the full bounded-label domain (event types, dialectic/deriver/dreamer tokens) and adds a new message_embeddings_pending gauge tracking the embedding backlog.

1. Must fix before merge

  • [CI] mergeStateStatus is BLOCKED. All individual checks pass, but branch protection rules don't see them as satisfying the required-checks gate. A repo admin needs to either add the correct checks to the branch-protection rule or remove the stale requirement. This is not a code defect — it's a repo-configuration gap that prevents ANY merge, regardless of code quality.
  • [CI] / [DEFECT] No required CI checks are registered on this branch. gh pr checks 927 --required exits 1 with "no required checks reported". While individual checks do run (basedpyright, test-python), they aren't gating. A human should decide whether these should become required before this (or any future) PR can merge.

2. Should fix soon

  • [AC-N] Scope: The PR body describes pre-creating telemetry_events_dropped children at 0, but the actual diff does significantly more:

    • ALL_EVENT_TYPES + HIGH_VOLUME_EVENT_TYPES registries (src/telemetry/events/init.py) — new infrastructure for enumerating event types
    • Full bounded-metrics init (initialize_bounded_metrics) covering dialectic tokens, deriver tokens, dreamer tokens, embed-now gauges, and buffer gauges — way beyond the stated dropped-metrics scope
    • message_embeddings_pending gauge + _record_pending_embeddings_backlog() DB query in src/reconciler/sync_vectors.py — an entirely separate feature (embedding-backlog tracking) bolted onto the same PR

    The code is well-written and tested, but these three additions each deserve their own PR with their own contract/testing story. A human should decide whether to accept the scope-creep as-is or request splitting.

3. Safe to merge

No — merge is mechanically blocked by BLOCKED status, and required CI is not configured. Code quality is high: fail-soft on init (never crashes startup), drift-guard tests prevent stale registries, cartesian-product explosion is avoided by explicit valid-combo tables, and the 294-line test suite is thorough.

Label notes

This reviewer's account does not have admin rights to plastic-labs/honcho, so labels cannot be applied via API. A repo admin should apply:

  • loop-changes-requested — CI gate is missing + scope concerns
  • needs-human-review — BLOCKED merge state needs admin attention

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.

3 participants