Skip to content

0.5.5 — 2026-09-15

The web console gains a probe editor and a settings page, so an agent is configured from the browser; enabling TLS no longer requires a certificate; the licence agreement is published and linked from the console, the installer and the CLI; the agent ships as a container image; agent config check reads the values, not only their shape; uninstall cleans the machine even without a service unit; a host with a saturated inotify quota keeps monitoring; OTLP outputs can push to a base path and report failures per signal; a probe's logs can be routed to one output; a Pro probe reads the console log stream of an Azure Container App; and four breaking changes on tags, channel names and metric timestamps, listed at the end.

Features

  • Enabling TLS no longer requires supplying a certificate. tls.enabled: true with nothing else now produces a self-signed pair on first start, next to the configuration at <config directory>/certs/, readable only by the service user. The log says the certificate is self-signed and how to replace it, rather than letting a browser warning be the first news of it. The pair is generated once: replacing those two files with your own keeps them, and a restart or an upgrade will not undo your certificate.

    Naming a cert_file that does not exist is a different case and generates nothing. The agent refuses to start the listener and names the file and the directory it looked in, because a path you wrote and that is not there is more likely a typo than an invitation, and writing a certificate over it would hide the mistake.

  • The licence that covers the paid probes is published, and the agent says where to read it. The SenHub Agent licence agreement is now a page rather than a file to request: the French text, which is the only binding version, and an English courtesy translation carrying the same article numbering so a cross-reference resolves in either language, each with a printable PDF. The installer names it on Windows, the install command prints it on Linux, and the console's Settings page links to it from the activation field, so an operator can read what activating a token places under contract before doing it. Annex 1, the third-party component list, is generated from the build's dependency graph and describes what actually ships rather than what the dependency file declares.

  • The agent ships as a container image. ghcr.io/senhub-io/senhub-agent starts from one required variable and one mount: an export token, and a volume on the state directory. An entrypoint writes a configuration from the environment when none is mounted and checks it before the agent starts, so a container whose variables produce a file the agent would refuse stops with the reason instead of restarting in a loop. Probes come through three doors: SENHUB_PROBES carrying the YAML a probes.d file would hold, a mounted probes.d, or a whole mounted configuration directory. Credentials are never written to a file; the fragments carry ${env:...} references the agent resolves at every start. Linux amd64 and arm64, running as an unprivileged user, with a health check on the console.

    Identity is the part worth reading twice. A container has no machine-id, and host.id is read from it, so without one the underlying library falls back to the kernel boot identifier and every container arrives as a new host: measured on a real deployment, three runs of the same image, three hosts in the graph. The agent key has the same shape of problem for the other identity, since it lives in agent.yaml. Both are now resolved at startup and kept in the state volume, so one volume is one host and one agent, and SENHUB_HOST_ID settles the first where the deployment already knows it. Nothing is baked into the image: an identity that belongs to an image belongs to all of its instances at once.

  • The web console puts what you need to start in front, and shows where the data goes. The menu now follows the operator's path: Overview, Probes, Outputs, Settings, Docs. The probe editor opens on a block "Required to start" (name, enabled, and the parameters the probe does nothing useful without: host, user and password for a database, target, version and community for SNMP), with a sticky action bar and every optional group folded behind a summary of what is set. Errors are anchored on their field, a stored secret is shown as a state with a Replace button, the type is chosen in a filterable grid, and a fresh install offers four starters instead of an empty table. The new Outputs page lists every output with its state (listening, exporting, idle, failing with the reason, disabled) and who last read each endpoint; push outputs are created and edited from a form generated from their schema, and an OTLP output is tested step by step (name resolution, TCP, TLS, one real export) before it is saved. The Sensor Builder became the Sensor URLs tab of the HTTP output; the old address redirects there. The Overview replaces the dashboard with a getting-started list, compact probe and output summaries, one agent card, a licence card without the list of sixty names, and the recent events of the agent (a probe that started failing, an output that could not start, a save from the console). The Docs entry opens the documentation on agent.senhub.io. Scripts get the same through /api/{key}/catalog/outputs, /api/{key}/config/outputs and /api/{key}/info/events.

    A few details an operator will meet. An output can be created disabled: its file is written as .disabled from the start. A file under strategies.d the agent cannot read is listed as failing with its error instead of being hidden. An edit that does not mention enabled leaves a disabled output disabled. A stored secret is shown as Stored and never sent back by the form, inside a block or a list of blocks (the users of an SNMP v3 device) as well; Replace, then an empty field, drops it. The probe catalogue tells a type that needs a licence from one that does not run on this operating system.

  • Every probe type says what it reads. All 66 probe types of the agent now carry a parameter schema: the keys the probe reads, their kind and default, which ones it cannot start without, which ones are secrets. The console's editor is generated from it, so every type is added and edited from the Probes page with the same form, Validate reports a key the probe never reads or a value of the wrong shape before anything is written, and the catalogue lists every type with its tier and where it runs.

  • Push OTLP straight to a backend that serves it under a base path. endpoint is a host:port pair and cannot carry a path, which ruled out backends exposing OTLP under a prefix. The new url_path_prefix (OTLP/HTTP only) is prepended to the standard signal paths, so /api/v2/otlp sends to /api/v2/otlp/v1/metrics and its siblings. Setting it with protocol: grpc is refused at config load rather than silently ignored. Documented with a Dynatrace example.

  • Close idle OTLP/HTTP connections before the ingress does. A sparse signal (logs, typically) can leave its connection idle long enough for a load balancer to close it, and the agent then pays a failed request discovering it. The new idle_conn_timeout, set below your ingress idle timeout, makes the agent close first and reconnect cleanly. Unset keeps the previous behaviour (the Go default of 90 seconds), so nothing changes until you configure it.

  • Route a probe's logs to a specific output. A probe that produces logs sent them to every output that reads them; the endpoints-style filtering that governs metrics did not apply. The new per-probe log_strategies closes that: log_strategies: ["otlp"] sends that probe's records to OTLP and nowhere else. Omitting it keeps today's behaviour, so nothing changes until you configure it. Only outputs that can actually consume logs are accepted — agent config check rejects anything else instead of letting the records go nowhere.

  • Say who owns what a probe observes, and which application it belongs to. Governance (owner, criticality, location, lifecycle, labels) could only be stated for the host, in the OTLP output, or per device inside snmp_poll. A database on a host had the host's governance, and so did the one next to it that belongs to another application. Every probe entry now takes an optional governance block with the same vocabulary; it is stamped on the entities, metrics and logs of that probe only. The label application names the application chain an instance takes part in, so one filter finds everything in the chain across hosts and probe types. agent config check reports an unknown key or a value outside a closed set, and the Probes page edits the block below the parameters. The host's location (site, datacenter, rack, room) now also descends to what runs on the host, a database or a service reached on the loopback address; owner, criticality and labels never do.

  • Read the console log stream of an Azure Container App (Pro). The new azure_container_apps probe reads stdout and stderr of every replica of an application through Azure Resource Manager, the same stream az containerapp logs show --follow reads, without a volume, a sidecar or any change to the container. Lines ride the log rail like filetail lines, with the same parsers and multiline folding, and carry the application, revision, replica and container they came from. Replicas that appear and disappear are followed; the probe reports its open streams and reconnections so a broken stream shows in PRTG even when the logs go elsewhere. min_severity and exclude drop the lines nobody reads before they leave the agent, and a counter shows what was left out. The probe also reports the reads left in the subscription's Azure Resource Manager budget (senhub.azure_container_apps.arm.reads_remaining), so a scan interval that eats into the quota is visible.

Fixes

  • A metric point is stamped with the moment it was measured, not the moment it left. A series in the store is re-published on every push cycle until it is evicted, and every copy carried the export time, so an old measurement was presented as a current one for up to a full staleness_ttl window. An operator saw it on a target removed from an http_check probe: the series came back from the checkpoint after a restart and stayed red at 0, with fresh timestamps, on something nobody was querying. The datapoint now carries the time the probe observed it; a value the agent computes at export time, such as its own self-metrics, keeps the export time. Eviction is unchanged and still removes the series after the window. This has a consequence for probes that run less often than the push cycle, described under Breaking Changes. (#812)

  • A container without a mounted volume now says so. The entrypoint has always warned when the state directory is not a mount, because without one the host identity, the agent key and the log bookmarks go with the container. Under Docker the warning could not fire: the image declared VOLUME on that directory, so Docker created an anonymous volume, the mount check was satisfied and nothing was said — while the volume was orphaned by docker rm and the next run started as a new host anyway. The directive is gone. A named volume behaves as before and keeps its identity across a docker rm; a run without one is told what it is losing.

  • A configured minimum TLS version now reaches the listener. min_tls_version was parsed, defaulted, logged at startup and returned by the configuration API, and never applied: ServeTLS ran on a server whose TLSConfig was nil. An operator who required TLS 1.3 was served by a socket that still accepted 1.2, with the agent's own log line telling them 1.3 was in force. A security control that is announced and not applied is worse than one that is absent, because it is the one nobody re-checks.

  • A missing certificate no longer reports a listening HTTPS server. Enabling TLS without usable certificate files logged "HTTPS server listening", naming the configured minimum version, and only then discovered the file was absent. The port then refused every connection while the service still reported active: the console, the PRTG endpoint and the Nagios endpoint were gone with nothing on screen saying so. The default made it easy to reach, since ./certs/agent-cert.pem resolves against the unit's working directory while an install writes its certificates beside the configuration — the producer and the consumer never agreed on the path, and the log printed the relative form, which reads as the configuration directory. Both files are now checked before anything is announced, and the failure names the absolute paths.

  • The console URL printed after an install carries the key. It said http://localhost:8080/web/{agentkey}/dashboard, with the placeholder left in, two lines after generating the key. It is the last line an operator reads and the only one telling them where to go, and it could not be opened.

  • A clean first install no longer opens with two warnings. Sealing inline secrets and migrating an inline licence both read the configuration. On a first install there is none yet — it is written a few lines later — so both failed and both warned, naming a missing file. Neither has anything to do when the file is absent, so neither runs.

  • uninstall says how much it removed. It reported "removed 0 files and 3 directories" after deleting a tree holding agent.yaml, license.jwt, the sealed secret store and the fragment directories: the counter reported the top-level file list while RemoveAll took everything under the directories without counting any of it. On a destructive command the summary is the one line an operator reads to confirm what happened, and "0 files" on a tree holding a licence token reads like nothing was removed.

  • The Overview no longer scrolls sideways on a phone. Its probe table is wider than a 390px screen and pushed the whole document to 589px. Wrapping the table was not enough: the grid track holding the card would not shrink past it, since a bare 1fr track is minmax(auto, 1fr) and its automatic minimum is the width of what it holds. The card grids now use minmax(0, 1fr) and the table scrolls inside its card.

  • One spelling for "license", everywhere it is read. The Settings page, the Overview, the probe picker and the probe editor disagreed within a single session, and the API supplied part of it: the catalogue returned "requires a licence" as the reason string the picker renders verbatim. The Windows installer disagreed with itself too, on the Options page description, the licence-file label, and the file picker's title and filter.

  • A probe whose target is down is no longer shown as healthy. A database probe pointed at a host that refuses the connection was listed as running, counted under "Running", and left the Overview saying "0 failing", while the agent logged the refused ping and emitted db.up = 0. Health was right: a refused connection is a successful collection reporting a down target. What was missing is the other half, and the probes already publish it, since 49 types emit a senhub.<area>.up series. Such a probe now has its own state, "target down", naming the series it quotes, sorted to the top of the list and counted where an operator looks for what needs them.

  • Deletions ask in the page. They used the browser's own dialog, the one interaction that left the console's style: unstyleable, blocking, and silently disabled for good once a viewer ticks Chrome's "prevent this page from creating more dialogs". Escape cancels, Enter confirms, and focus starts on Cancel.

  • The probe editor no longer contradicts itself. Validate reported the same missing field twice, once in the form's words and once in the schema's, so a blank PostgreSQL form announced seven problems for four empty fields. And nothing cleared as the operator typed: with all four filled, the page said "Ready to save" in green while still showing the red banner, the four field errors and a stale problem count. A verdict answers the values it was run on, so any edit now drops it.

  • A probe or output created disabled is no longer announced as started. Both said "the agent starts it on its own", in green, directly above a row reading "disabled".

  • The address bar names the tab on screen. Arriving on the HTTP output's Settings tab left the address reading #urls, so a copied link or a plain reload landed on the other tab. The licence file control also drew its labels in the browser's language, putting French text in the middle of an English console; it is now the console's own button.

  • A poller can no longer chart a measurement the agent never reported. The pull endpoints sent no cache directive at all. A response with neither a validator nor an expiry lets a client apply its own heuristic freshness, so PRTG, Nagios, a Prometheus scraper or any proxy between them was free to reuse an earlier answer, and nothing on either side would say so. Every route the HTTP output serves now says no-store. The console's own assets keep revalidating, where caching is safe and useful.

  • The sensor URL preview follows the URL instead of waiting for a click. Every choice on the Sensor URLs tab (poller, probe, tag filter, whether channel names carry the resource) changes what the poller would receive, so after any of them the panel below showed the answer to a URL no longer on screen, while reading as the current one. It now refreshes on its own, debounced so a tag filter picked value by value sends one request; the button remains as an explicit re-read and is named Refresh. The view also opens on the raw response rather than the channel table: it is what the poller actually receives, and the only view that shows a field the table does not carry. The table is one click away, and whichever is picked is the one that comes back next time.

  • A container host no longer drowns the agent's own log. On a machine that runs containers, veth interfaces appear and disappear constantly. The network probe enumerates the counters, then looks each interface up by name; between the two, a veth can be gone. That race is the normal state of a container host, not a failure, and it was reported straight to standard output with no level, no structured field and no respect for the configured verbosity. Measured on a fleet host with 26 containers and 23 container creations in 24 hours: 42 of the agent's 87 journal lines were this one message, 48 per cent of its output, all of a single kind. Two machines with 6 and 7 containers but little churn produced none, which matches the mechanism: it is the birth and death of the interfaces that produces the race, not their number. The vanish case is now a Debug line through the logger, any other failure to read an interface stays a Warn, and both carry the interface as a structured field.

  • The depends_on warning no longer tells root to run as root. An agent that sees outbound sockets and can name none of their owners said so and advised running as root — while its own running_as field read root. The privilege answer is only half of it: mapping a socket to its owner reads /proc/<pid>/fd, and unreadable is one reason, absent is another. A container sharing another's network namespace sees that namespace's whole socket table while its /proc holds only its own processes, so the owners do not exist in its view and no privilege conjures them. Reproduced on a host, agent as root, every socket unattributable. The message now tells each case what is actually true, and both still name the switch to turn the rail off.

  • An omitted cache: block is no longer a warning. It was reported on every call — not once at load — so an accepted default produced a recurring WARN, which is how operators learn to read warnings as noise. The default is now silent, and a configured retention is announced once, again only if a reload changes it.

  • A list parameter written without its brackets is no longer read as nothing. signals: metrics, logs, traces is valid YAML and a string, not a list. The OTLP receiver discarded it and started on its default — metrics only — with nothing in the log to say the line had been ignored, while the sender got UNIMPLEMENTED for the two signals it thought it had enabled. That reads like a missing feature; it was an option nobody had asked for. A value that cannot be read as a list is now refused at startup, naming the shape to write. Absent, or an explicitly empty list, still means metrics only.

    The same silence existed on every list parameter the probes read — paths, channels, levels, sources, services, brokers, mib_paths, allowed_cidrs. They now report a value they cannot read instead of falling back without a word.

  • The agent no longer refuses to run when it cannot watch its configuration. The watch exists so an edit is picked up without a restart. It can fail for reasons that have nothing to do with your file: on Linux, inotify has a per-user quota of 128 instances by default, and a host running k3s can hold most of it. The agent treated that as a fatal start, systemd restarted it five times, gave up, and the host stopped being monitored — because a convenience could not start.

    It now starts, collects and exports normally, and says what it lost:

    Surface What you see
    Log one WARN naming the cause and the consequence
    agent status a Configuration NOT watched block with the reason
    Metrics senhub.agent.config.watch.disabled{reason} at 1, no series when the watch runs

    In that state a configuration change needs an agent restart to apply. A configuration that cannot be loaded — a malformed file, a broken substitution — still fails the start, as before: that one is yours to fix, and systemd is right to report it.

  • The docker and swarm probes now work on Windows. Docker Engine and Docker Desktop expose their API over a named pipe there, while the probes only ever dialled a Unix socket — so a Windows host running containers collected nothing at all. The default address is now the one the platform actually uses, and socket_path accepts a pipe (npipe://./pipe/docker_engine).

  • agent config check now reads the values, not only the shape. It verified that a file parsed, that a probe type existed and that a strategy resolved, and never asked whether the values would be accepted — so priority: 99 was reported [OK] and the probe then refused to start, and a strategy key the agent rejects at construction passed the same way. The check now runs each probe's own validation (the probe is built and dropped: nothing is started, no socket or file is opened) and each strategy's validator.

    Three new lines you may see on a file that used to pass:

    What Reported as
    A value the probe refuses (priority: 99) [ERROR], and the check exits non-zero
    A value the probe cannot read (priority: "high") [WARN] — the probe starts on its default, which is not what the file says
    A key the strategy never reads (insecure: true) [ERROR], or a [WARN] naming the current spelling when there is one

    If you run config check in a pipeline, a file with one of these now fails it. That is the point — the agent was already behaving this way, silently.

  • A parameter the agent cannot read no longer disappears without a word. The typed readers answered "absent" and "present but unreadable" identically, so every probe treated a mistyped value the way it treats a missing one: it used its default. priority: "pas-un-nombre" started a journal reader on priority 7 and said nothing. The agent now warns at startup, naming the parameter, the value written and what was expected.

  • uninstall cleans the machine even when there is no service unit to remove. The file cleanup was gated on the service removal succeeding, and the failure was swallowed: on a machine whose unit had already been removed by hand, or where an install never completed, uninstall --yes printed nothing, removed nothing and exited 0 — leaving the sealed secret store, which holds the agent key and every credential the agent sealed. The removal is now reported when it fails, the files go either way, and the exit code says so.

  • Uninstalling now removes the sealed secret store. uninstall said "Cleanup completed" while leaving probes.d/, strategies.d/ and the secret store on disk — the latter holding the agent key and every credential the agent had sealed (database, BMC, API tokens). The whole installed configuration directory is now removed, and the confirmation says so before you answer, because it cannot be undone. A configuration you pointed at with --config-path outside the install directory is still treated narrowly: only that file is removed, since its neighbours are not ours to delete.

  • A second agent on the same machine no longer destroys the first one's logs. Two agents resolved the same log file and each rotated it independently; whichever rotated first truncated the other's file, and its history was gone. A normal install keeps writing senhubagent.log exactly as before — nothing to change in your log collection. A second instance, started with a different configuration file, now writes to its own.

  • A registry URL with a path no longer breaks updates silently. auto_update.url is the base the agent appends to, and it appends two different things — /releases/… for the version list, /download/… for the artifacts. A value that already carried one of those paths made every derived URL double it, so the agent found nothing, changed nothing, and stayed on its installed version with auto_update.enabled: true still in the file. The agent now corrects every shape anyone writes (/releases, /download, a pasted releases.json URL, a trailing slash), on every code path rather than only when the configuration is read, and agent config check reports the value with the one to write instead. The correction is also logged once rather than on every collection cycle.

  • The OTLP log queue no longer fills with records that cannot be delivered. When the receiver refuses a batch for what it contains — a malformed payload, an attribute type it does not accept — resending it gets the same answer. Those records used to be written to the on-disk queue anyway, replayed at every restart and every recovery, refused again, and written back; they occupied space the queue then took from records that a retry would have delivered. They are now discarded once and counted, and the queue keeps doing what it is for: riding out an outage.

  • A batch the collector partly refuses is no longer treated as a batch it never received. OTLP lets a consumer accept a batch and keep only part of it, saying what it refused and why. The agent read that answer as a failed export: the delivered batch went to the on-disk log queue and was replayed at every restart and recovery, re-sending records the collector already had, while the refused ones were refused again and nothing counted them. The rejection is now separated from a real failure — the records the consumer refused are counted in the new senhub.agent.export.rejected{strategy,signal} and the reason it gave is logged (once per minute per reason, not once per batch), while the batch stays out of the queue.

  • MySQL and PostgreSQL answer to their documented parameters again. Both pages described the parameters of the paid probes they replaced when those probes moved to the free tier. An operator following them set sslmode: require on PostgreSQL and got a plaintext connection; set expose_top_tables on MySQL and got nothing. Nothing warned, because a parameter a probe does not read is simply not read.

The old names work again — expose_per_database, expose_top_tables, database, sslmode, sslrootcert, timeout — and the options that had no equivalent at all are back: a query timeout on both probes, a private CA and a verification switch on MySQL (tls now takes a block, not just true), and a configurable replication lag threshold. The pages list what the probes actually read, and agent config check names the current spelling for a renamed parameter.

Three PostgreSQL parameters are not coming back: expose_per_database, expose_top_tables and bloat_top_n asked for per-database and per-table breakdowns this probe does not produce. agent config check reports them as errors rather than letting them sit in a file looking like they do something.

  • A lagging MySQL replica is no longer reported as healthy. senhub.db.replication.health checked that both replica threads were running and stopped there, so a replica hours behind its source — stale reads, useless for failover — reported 1. Lag is part of the composite now, as the reference always said it was, above max_replication_lag_seconds (default 300, the threshold the PostgreSQL probe has always applied). Set it to 0 for a deliberately delayed replica. If you alert on this metric, a replica between your own tolerance and 300 seconds will change state at upgrade.

  • Options that shipped without documentation now have some. A test reads the configuration keys the agent parses and fails the build when one appears nowhere in this guide, which found sixteen: the OTLP memory limit and durability settings, the metric cache cap, the push storages' own parameters, and per-probe options on smart, nvidia, activemq and snmp_poll. Each was a shipped feature nobody could find.

  • A dead listener is now reported as unhealthy. The syslog, event and OTLP receiver probes wait for data to arrive rather than polling for it, so they had nothing to derive health from and reported healthy on every cycle — including cycles where their socket had been closed for hours because the port was taken at startup or the server stopped with an error on its own goroutine. They now report the state of the listener itself, so senhub-agent status and senhub_agent_probes_healthy tell you when one has stopped receiving.

  • A probe can no longer ship untagged data by omission. probe_name and probe_type are now added centrally to every datapoint that does not already carry them. Previously each probe added them itself, and a probe that forgot produced series nothing could tell apart — and whose cache entries collided with another instance's.

  • An intake outage can no longer grow the event backlog until the agent dies. The cloud metrics and PRTG outputs already capped what they hold when a destination is unreachable; the event output did not, and every failed send appended the whole batch to a list nothing trimmed. It is now bounded like the others: past the cap the oldest events go first, and the loss is counted rather than silent.

  • A rejected batch is no longer resent forever. When the intake refuses a batch of events for what it contains — a malformed payload, an unprocessable body — resending the same bytes gets the same answer. The agent now recognises that class, drops the batch once, and moves on, instead of pinning it at the head of the retry backlog where it blocked everything queued behind it and burned a round-trip (plus two seconds of retry sleep) on every cycle.

  • A failing output is now visible before it starts losing data. Until the backlog reaches its cap an output that cannot deliver sheds nothing, so no counter moved and the only trace was a log line. The new senhub.agent.export.send.failed{strategy,reason} counts failed delivery attempts per output, with reason saying whether the batch was kept (transport) or dropped (validation, configuration).

  • Stopping the agent no longer races itself. Every service — the configuration loader, the outputs, the probe pool, the auto-updater — now shares one cancellation, and each gets its own shutdown allowance instead of competing for a single five-second budget the first slow drain could consume entirely. In practice: an output that takes its time flushing its last batch no longer costs the probe pool its chance to close connections cleanly. The whole stop stays bounded (twenty seconds at worst) so systemd and the Windows service manager never escalate to a kill.

  • A configuration reload no longer leaves work behind. Probes and outputs recreated by a reload used to be started without any link to the agent's own shutdown, and relied entirely on being stopped individually; one that was not left a goroutine running for the life of the process. Long-lived agents whose configuration is edited regularly were the ones that accumulated them. A start/reload/stop cycle is now covered by a test that fails on a single leaked goroutine.

  • OTLP export failures are now visible per signal. A failing logs pipeline (for example a receiver rejecting every batch) previously moved no counter: the failure only surfaced as dead-letter queue churn. Failed exports now increment senhub.agent.otlp.export.errors for every signal, the logs path logs a throttled warning naming the error, and senhub-agent status --otlp, the web dashboard JSON (/info/otlp, export_errors_by_signal) and the exported metric carry the per-signal breakdown.

  • refresh-unit no longer drops directives you added to the unit. A directive added inline to senhub-agent.service (an EnvironmentFile= carrying an ingest token, an Environment= proxy setting) was silently removed when the unit was refreshed from the packaged template, which could leave an output starting without its credentials. Directives the template does not manage are now carried over into the refreshed unit, under a comment saying where they came from. Hardening directives, ExecStart, User= and Group= are still decided by the refresh.
  • A configured output that fails to start is now visible. Until now the only trace was one error line at boot: the agent kept running with its remaining outputs, so a broken one could go unnoticed for as long as nobody read the journal. A strategy that is configured but not running is reported by senhub-agent status, and exported as senhub.agent.strategy.failed{strategy,reason} (no series once every output runs). Fixing the configuration clears it on the next reload, without restarting the agent.

Breaking Changes

  • linux_logs and windows_eventlog no longer put host.name on a log record. Both probes read a local journal, and both copied the hostname the source spells into a record-level host.name — the short kernel name on Linux, the uppercase computer name on Windows. The resource already carries the canonical FQDN, the same value the metrics use, so every record held that key twice with two values and which one survived was decided by the consumer. On a reporting tenant that split 22 machines into 35 identities, and a dashboard filtered on the FQDN showed a quarter of the lines with nothing saying so. The per-record value is kept, namespaced, the way syslog has always emitted a remote sender's hostname.

    Before (record attribute) After (record attribute)
    host.name = edge-01 (linux_logs) systemd.hostname = edge-01
    host.name = DASH483 (windows_eventlog) winlog.computer = DASH483

    Migration: a query filtering log records on the short or uppercase name must move to the resource-level host.name (the FQDN), which is also what joins logs to metrics. A query that needs the spelling the source used reads systemd.hostname or winlog.computer. The syslog probe is untouched: its syslog.hostname genuinely names another machine.

  • senhub.agent.otlp.export.errors gained a signal attribute. The unlabeled series is replaced by one series per failing signal; the former total is the sum over signal. A signal that never failed emits no series (counter semantics: absence means zero).

    Before After
    senhub.agent.otlp.export.errors (no attributes) senhub.agent.otlp.export.errors{signal="metrics"\|"logs"\|"traces"}

    Migration: replace senhub_agent_otlp_export_errors_total in dashboards and alert rules by sum(senhub_agent_otlp_export_errors_total) (any-signal alerting) or pivot on the new signal label. The export_errors_total field of /info/otlp is unchanged.

  • A series measured less often than the push cycle now yields one sample per measurement, not one per push. Since a point carries the time it was measured (see Fixes, #812), the copies re-published between two runs of a probe carry the same timestamp and a time-series database keeps one of them. Before, a probe running every 30 minutes produced a fresh sample every 30 seconds; now it produces one every 30 minutes. This changes what a consumer sees for exec, os_updates and any probe whose interval is longer than the push cycle: an instant query, or an alert rule with a short lookback, finds no sample once the last one is older than the database's staleness window (five minutes for Prometheus and VictoriaMetrics), and an alert on such a series resolves while the condition still holds.

    Migration: a rule or panel on a series measured every N minutes must look back at least N minutes. In PromQL, replace m == 1 by last_over_time(m[1h]) == 1, with a range longer than the probe's interval. Series measured at or above the push cadence, which is the general case, are unaffected.

    A later version re-publishes a value as current for as long as its probe still vouches for it, so that a low-cadence gauge behaves as before without the timestamp of an orphan series ever running ahead of its last measurement (#890).

  • PRTG push channels are now named like pull channels. The same measurement reached PRTG under two different names depending on how it travelled: the agent pushed cpu_core_usage while a PRTG pull showed CPU Core 0 Usage. Both now resolve the display name from the probe's definition, so one device has one channel set.

    Before (push) After (push and pull)
    cpu_core_usage CPU Core 0 Usage
    mem_used_percent Memory Used %

    PRTG treats a renamed channel as a new one, so a sensor fed by the push strategy will start fresh channels and lose the history attached to the old names. Plan the upgrade of push-fed sensors accordingly. Pull-fed sensors (the HTTP Data Advanced ones) are unaffected: their names do not change.

    A probe that sets an explicit prtg_metric_id still names its own channel — that override is unchanged.