main · last commit
13 days ago ·
7g0stsfu
ah-1cx.3 config/wiring: whitespace-only notifier field passes validation then fatally aborts boot
Past Stand
bd reopen ah-1cx.3
| Created by | Eugene Blikh |
| Owner | bigbes@gmail.com |
| Created | 2026-07-19T23:37:36Z |
| Started | 2026-08-04T23:32:44Z |
| Updated | 2026-08-04T23:41:51Z |
| Closed | 2026-08-04T23:41:51Z |
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.
ah-1cx
— Hardening: post-audit bug sweep (2026-07-20)
parent-child
open
Nothing depends on this issue.
| 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 |
| issue_id | ah-1cx.3 |
| label | milestone:v0.1.0 |
| 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 |
| 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 |
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.