{"record":{"id":"f1bf9381acb1eb85","repo":"zeroclaw-labs/zeroclaw","slug":"field-name-must-not-be-empty","errorCode":null,"errorMessage":"{field_name} must not be empty","messagePattern":"(.+?) must not be empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-memory/src/postgres.rs","lineNumber":333,"sourceCode":"            let result = f();\n            let _ = tx.send(result);\n        })\n        .context(\"failed to spawn PostgreSQL operation thread\")?;\n\n    rx.await.map_err(|_| {\n        ::zeroclaw_log::record!(\n            ERROR,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                .with_outcome(::zeroclaw_log::EventOutcome::Failure),\n            \"PostgreSQL operation thread terminated unexpectedly\"\n        );\n        anyhow::Error::msg(\"PostgreSQL operation thread terminated unexpectedly\")\n    })?\n}\n\npub(super) fn validate_identifier(value: &str, field_name: &str) -> Result<()> {\n    if value.is_empty() {\n        anyhow::bail!(\"{field_name} must not be empty\");\n    }\n\n    let mut chars = value.chars();\n    let Some(first) = chars.next() else {\n        anyhow::bail!(\"{field_name} must not be empty\");\n    };\n\n    if !(first.is_ascii_alphabetic() || first == '_') {\n        anyhow::bail!(\"{field_name} must start with an ASCII letter or underscore; got '{value}'\");\n    }\n\n    if !chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_') {\n        anyhow::bail!(\n            \"{field_name} can only contain ASCII letters, numbers, and underscores; got '{value}'\"\n        );\n    }\n\n    Ok(())","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-memory/src/postgres.rs#L315-L351","documentation":"validate_identifier guards PostgreSQL identifiers (schema and table names) that are interpolated directly into SQL text. This first check fires when the identifier string is empty — a required name was never set. The function is called from the store constructor (new) and validated_schema_identifier, so it typically surfaces at backend creation time.","triggerScenarios":"Constructing the postgres memory store with an empty schema or table identifier: a defaulted config field left blank (schema = \"\" in [storage.postgres.<alias>]), or an identifier derived from an alias that sanitizes down to the empty string.","commonSituations":"Optional storage config fields left empty in TOML; alias-derived identifiers where the alias consists only of characters that get stripped; programmatic construction passing an unpopulated struct field.","solutions":["Set the missing schema/table identifier in `[storage.postgres.<alias>]`.","If deriving identifiers from agent or storage aliases, apply a sanitizer that falls back to a default when the result is empty."],"exampleFix":"# before\n[storage.postgres.main]\nschema = \"\"\n\n# after\n[storage.postgres.main]\nschema = \"zeroclaw\"","handlingStrategy":"validation","validationCode":"let pg = &config.storage.postgres[alias];\nif pg.schema.as_deref().unwrap_or(\"\").trim().is_empty()\n    || pg.table.as_deref().unwrap_or(\"\").trim().is_empty()\n{\n    anyhow::bail!(\"postgres storage alias '{alias}' is missing a schema or table name\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make schema/table required fields in your config validation layer so empty values fail at load time.","When deriving identifiers from aliases, sanitize then assert non-empty before use."],"tags":["postgres","validation","identifier","sql","config"],"backgroundTag":"invalid-sql-identifier","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}