~bigbes/agents-dev · parade

main · last commit 13 days ago · 7g0stsfu

← Back to the parade

ah-1cx.3 config/wiring: whitespace-only notifier field passes validation then fatally aborts boot Past Stand

status: closed P3 bug milestone:v0.1.0
bd reopen ah-1cx.3
Created byEugene Blikh
Ownerbigbes@gmail.com
Created2026-07-19T23:37:36Z
Started2026-08-04T23:32:44Z
Updated2026-08-04T23:41:51Z
Closed2026-08-04T23:41:51Z
Description
Ntfy/Telegram blocks are stored untrimmed (config.go:453) and present() decides configuration by trimmed emptiness, but reconcileDeps (cmd/agenthubd/main.go:292,298) selects the notifier with raw non-empty checks. A whitespace-only ntfy.url (literal or via VAR expansion) is absent to validation yet present to wiring: config.Load succeeds, then ntfy.New rejects it and run() exits 1 — killing a deployment whose telegram block is fully valid. This contradicts present()'s own documented invariant. Fix: trim Ntfy/Telegram in resolve (as yonote/mem0 already are) or make reconcileDeps use present(). Found by adapter audit 2026-07-20.

Depends on

  • ah-1cx — Hardening: post-audit bug sweep (2026-07-20) parent-child open

Depended on by

Nothing depends on this issue.

No comments.

Close reason

Fixed in a8f264e. Root cause: the optional blocks' present() predicates decide configuration by TRIMMED emptiness, but resolve copied Ntfy/Telegram straight out of the YAML untrimmed and reconcileDeps (cmd/agenthubd/main.go:292,298) selected the notifier with raw non-empty tests — so a whitespace-only ntfy.url was ABSENT to validation and PRESENT to wiring: config.Load succeeded, ntfy.New then rejected the value and run() exited 1, killing a deployment whose telegram block was perfectly valid. Fixed at the root by making 'set but whitespace-only' a load-time error instead of a value that silently reads as unset: resolve now scans the RAW decoded document (before any trimming erases the evidence) via blankScanFields()+validateNoBlankValues() and reports '<field> is set to whitespace only: remove the key to leave it unset, or give it a real value'. The scan is deliberately GENERIC, covering every field a present()/required check reads through TrimSpace: vikunja.token, vikunja.webhook_secret, both agentsview fields, both ntfy fields, telegram.token/api_base, yonote.base_url/token/publish_collection_id/qa.role/qa.bucket/qa.collections[i], both mem0 fields — collections entries indexed by name because trimNonEmpty would otherwise drop a blank one and silently shrink the watched set. resolve also now trims the four notifier strings the way yonote/mem0 base_urls already were, so the value the adapter constructor receives is exactly the value present() judged; this also stops a merely PADDED value (a ${NTFY_URL} picking up a trailing newline from the env file) from reaching ntfy.New. reconcileDeps keeps its raw tests but now carries a comment recording why they are equivalent to present() and that any new notifier field must be trimmed at load rather than compared there. Cover: TestLoadBlankValues — the original scenario, the ${VAR}-expands-to-blank variant, a table over all sibling fields, and padded-but-real values surviving trimmed with present() true.
  • Eugene Blikh added under epic ah-1cx · 2026-07-20T02:37:35Z
  • Eugene Blikh created the issue · 2026-07-20T02:37:35Z
  • Eugene Blikh added label milestone:hardening · 2026-07-20T03:08:20Z
  • Eugene Blikh removed label milestone:hardening · 2026-07-20T03:10:15Z
  • Eugene Blikh added label milestone:v0.1.0 · 2026-07-20T03:11:24Z
  • Eugene Blikh changed status to in_progress · 2026-08-05T02:32:43Z
  • Eugene Blikh closed the issue · 2026-08-05T02:41:51Z
    Fixed in a8f264e. Root cause: the optional blocks' present() predicates decide configuration by TRIMMED emptiness, but resolve copied Ntfy/Telegram straight out of the YAML untrimmed and reconcileDeps (cmd/agenthubd/main.go:292,298) selected the notifier with raw non-empty tests — so a whitespace-only ntfy.url was ABSENT to validation and PRESENT to wiring: config.Load succeeded, ntfy.New then rejected the value and run() exited 1, killing a deployment whose telegram block was perfectly valid. Fixed at the root by making 'set but whitespace-only' a load-time error instead of a value that silently reads as unset: resolve now scans the RAW decoded document (before any trimming erases the evidence) via blankScanFields()+validateNoBlankValues() and reports '<field> is set to whitespace only: remove the key to leave it unset, or give it a real value'. The scan is deliberately GENERIC, covering every field a present()/required check reads through TrimSpace: vikunja.token, vikunja.webhook_secret, both agentsview fields, both ntfy fields, telegram.token/api_base, yonote.base_url/token/publish_collection_id/qa.role/qa.bucket/qa.collections[i], both mem0 fields — collections entries indexed by name because trimNonEmpty would otherwise drop a blank one and silently shrink the watched set. resolve also now trims the four notifier strings the way yonote/mem0 base_urls already were, so the value the adapter constructor receives is exactly the value present() judged; this also stops a merely PADDED value (a ${NTFY_URL} picking up a trailing newline from the env file) from reaching ntfy.New. reconcileDeps keeps its raw tests but now carries a comment recording why they are equivalent to present() and that any new notifier field must be trimmed at load rather than compared there. Cover: TestLoadBlankValues — the original scenario, the ${VAR}-expands-to-blank variant, a table over all sibling fields, and padded-but-real values surviving trimmed with present() true.
Stored rows — what this pane was built from, as read
issues 1 row
id ah-1cx.3
content_hash b02d6efc693d9bd277063ae566b1ff77db50cfc1652da0418ad14b88bb47ed5c
title config/wiring: whitespace-only notifier field passes validation then fatally aborts boot
description Ntfy/Telegram blocks are stored untrimmed (config.go:453) and present() decides configuration by trimmed emptiness, but reconcileDeps (cmd/agenthubd/main.go:292,298) selects the notifier with raw non-empty checks. A whitespace-only ntfy.url (literal or via VAR expansion) is absent to validation yet present to wiring: config.Load succeeds, then ntfy.New rejects it and run() exits 1 — killing a deployment whose telegram block is fully valid. This contradicts present()'s own documented invariant. Fix: trim Ntfy/Telegram in resolve (as yonote/mem0 already are) or make reconcileDeps use present(). Found by adapter audit 2026-07-20.
design
acceptance_criteria
notes
status closed
priority 3
issue_type bug
assignee NULL
estimated_minutes NULL
created_at 2026-07-19T23:37:36Z
created_by Eugene Blikh
owner bigbes@gmail.com
updated_at 2026-08-04T23:41:51Z
closed_at 2026-08-04T23:41:51Z
closed_by_session
external_ref NULL
spec_id
compaction_level 0
compacted_at NULL
compacted_at_commit NULL
original_size NULL
sender
ephemeral 0
wisp_type
pinned 0
is_template 0
mol_type
work_type
source_system
metadata �{}
source_repo
close_reason Fixed in a8f264e. Root cause: the optional blocks' present() predicates decide configuration by TRIMMED emptiness, but resolve copied Ntfy/Telegram straight out of the YAML untrimmed and reconcileDeps (cmd/agenthubd/main.go:292,298) selected the notifier with raw non-empty tests — so a whitespace-only ntfy.url was ABSENT to validation and PRESENT to wiring: config.Load succeeded, ntfy.New then rejected the value and run() exited 1, killing a deployment whose telegram block was perfectly valid. Fixed at the root by making 'set but whitespace-only' a load-time error instead of a value that silently reads as unset: resolve now scans the RAW decoded document (before any trimming erases the evidence) via blankScanFields()+validateNoBlankValues() and reports '<field> is set to whitespace only: remove the key to leave it unset, or give it a real value'. The scan is deliberately GENERIC, covering every field a present()/required check reads through TrimSpace: vikunja.token, vikunja.webhook_secret, both agentsview fields, both ntfy fields, telegram.token/api_base, yonote.base_url/token/publish_collection_id/qa.role/qa.bucket/qa.collections[i], both mem0 fields — collections entries indexed by name because trimNonEmpty would otherwise drop a blank one and silently shrink the watched set. resolve also now trims the four notifier strings the way yonote/mem0 base_urls already were, so the value the adapter constructor receives is exactly the value present() judged; this also stops a merely PADDED value (a ${NTFY_URL} picking up a trailing newline from the env file) from reaching ntfy.New. reconcileDeps keeps its raw tests but now carries a comment recording why they are equivalent to present() and that any new notifier field must be trimmed at load rather than compared there. Cover: TestLoadBlankValues — the original scenario, the ${VAR}-expands-to-blank variant, a table over all sibling fields, and padded-but-real values surviving trimmed with present() true.
event_kind
actor
target
payload
await_type
await_id
timeout_ns 0
waiters
hook_bead
role_bead
agent_state
last_activity NULL
role_type
rig
due_at NULL
defer_until NULL
no_history 0
started_at 2026-08-04T23:32:44Z
is_blocked 0
labels 1 row
issue_id ah-1cx.3
label milestone:v0.1.0
dependencies 1 row
id 6d0bde92-cc65-5da4-958b-5e49a96d0226
issue_id ah-1cx.3
type parent-child
created_at 2026-07-20T02:37:35Z
created_by Eugene Blikh
metadata �{}
thread_id
depends_on_issue_id ah-1cx
depends_on_wisp_id NULL
depends_on_external NULL
events 6 rows
id 019f7cbe-045f-78ba-9327-3f5f5c0d7e58
issue_id ah-1cx.3
event_type created
actor Eugene Blikh
old_value
new_value
comment NULL
created_at 2026-07-20T02:37:35Z
id 019f7cda-2a1c-721e-b6d3-0fe71f84d374
issue_id ah-1cx.3
event_type label_added
actor Eugene Blikh
old_value NULL
new_value NULL
comment Added label: milestone:hardening
created_at 2026-07-20T03:08:20Z
id 019f7cdb-ed03-7e44-83fa-04610090e319
issue_id ah-1cx.3
event_type label_removed
actor Eugene Blikh
old_value NULL
new_value NULL
comment Removed label: milestone:hardening
created_at 2026-07-20T03:10:15Z
id 019f7cdc-fae9-7910-8a54-37a984798ccb
issue_id ah-1cx.3
event_type label_added
actor Eugene Blikh
old_value NULL
new_value NULL
comment Added label: milestone:v0.1.0
created_at 2026-07-20T03:11:24Z
id 019fcf1f-4fd9-72b3-8460-d1f0909255a7
issue_id ah-1cx.3
event_type status_changed
actor Eugene Blikh
old_value {"id":"ah-1cx.3","title":"config/wiring: whitespace-only notifier field passes validation then fatally aborts boot","description":"Ntfy/Telegram blocks are stored untrimmed (config.go:453) and present() decides configuration by trimmed emptiness, but reconcileDeps (cmd/agenthubd/main.go:292,298) selects the notifier with raw non-empty checks. A whitespace-only ntfy.url (literal or via VAR expansion) is absent to validation yet present to wiring: config.Load succeeds, then ntfy.New rejects it and run() exits 1 — killing a deployment whose telegram block is fully valid. This contradicts present()'s own documented invariant. Fix: trim Ntfy/Telegram in resolve (as yonote/mem0 already are) or make reconcileDeps use present(). Found by adapter audit 2026-07-20.","status":"open","priority":3,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-19T23:37:36Z","created_by":"Eugene Blikh","updated_at":"2026-07-19T23:37:36Z","labels":["milestone:v0.1.0"]}
new_value {"status":"in_progress"}
comment NULL
created_at 2026-08-05T02:32:43Z
id 019fcf27-aa32-7340-bfec-5a07cab35cf3
issue_id ah-1cx.3
event_type closed
actor Eugene Blikh
old_value
new_value Fixed in a8f264e. Root cause: the optional blocks' present() predicates decide configuration by TRIMMED emptiness, but resolve copied Ntfy/Telegram straight out of the YAML untrimmed and reconcileDeps (cmd/agenthubd/main.go:292,298) selected the notifier with raw non-empty tests — so a whitespace-only ntfy.url was ABSENT to validation and PRESENT to wiring: config.Load succeeded, ntfy.New then rejected the value and run() exited 1, killing a deployment whose telegram block was perfectly valid. Fixed at the root by making 'set but whitespace-only' a load-time error instead of a value that silently reads as unset: resolve now scans the RAW decoded document (before any trimming erases the evidence) via blankScanFields()+validateNoBlankValues() and reports '<field> is set to whitespace only: remove the key to leave it unset, or give it a real value'. The scan is deliberately GENERIC, covering every field a present()/required check reads through TrimSpace: vikunja.token, vikunja.webhook_secret, both agentsview fields, both ntfy fields, telegram.token/api_base, yonote.base_url/token/publish_collection_id/qa.role/qa.bucket/qa.collections[i], both mem0 fields — collections entries indexed by name because trimNonEmpty would otherwise drop a blank one and silently shrink the watched set. resolve also now trims the four notifier strings the way yonote/mem0 base_urls already were, so the value the adapter constructor receives is exactly the value present() judged; this also stops a merely PADDED value (a ${NTFY_URL} picking up a trailing newline from the env file) from reaching ntfy.New. reconcileDeps keeps its raw tests but now carries a comment recording why they are equivalent to present() and that any new notifier field must be trimmed at load rather than compared there. Cover: TestLoadBlankValues — the original scenario, the ${VAR}-expands-to-blank variant, a table over all sibling fields, and padded-but-real values surviving trimmed with present() true.
comment NULL
created_at 2026-08-05T02:41:51Z