zeroclaw-labs/zeroclaw · error
{field_name} must start with an ASCII letter or underscore;
Error message
{field_name} must start with an ASCII letter or underscore; got '{value}' What it means
The second identifier rule: PostgreSQL identifiers used by the memory store must start with an ASCII letter or underscore. Unquoted SQL identifiers cannot start with a digit, and since these names are formatted into query text, a leading digit or symbol would produce broken or dangerous SQL, so the constructor rejects it.
Source
Thrown at crates/zeroclaw-memory/src/postgres.rs:342
.with_outcome(::zeroclaw_log::EventOutcome::Failure),
"PostgreSQL operation thread terminated unexpectedly"
);
anyhow::Error::msg("PostgreSQL operation thread terminated unexpectedly")
})?
}
pub(super) fn validate_identifier(value: &str, field_name: &str) -> Result<()> {
if value.is_empty() {
anyhow::bail!("{field_name} must not be empty");
}
let mut chars = value.chars();
let Some(first) = chars.next() else {
anyhow::bail!("{field_name} must not be empty");
};
if !(first.is_ascii_alphabetic() || first == '_') {
anyhow::bail!("{field_name} must start with an ASCII letter or underscore; got '{value}'");
}
if !chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_') {
anyhow::bail!(
"{field_name} can only contain ASCII letters, numbers, and underscores; got '{value}'"
);
}
Ok(())
}
pub(super) fn quote_identifier(value: &str) -> String {
format!("\"{value}\"")
}
fn recall_time_filter(since: bool, until: bool, first_placeholder: usize) -> String {
match (since, until) {
(true, true) => format!(View on GitHub (pinned to 88bb9c8533)
Solutions
- Rename to start with a letter or underscore (archive_2026, _2026_archive).
- Apply a sanitizer that prefixes an underscore when the first character is a digit.
- Keep storage alias names in the same [A-Za-z_][A-Za-z0-9_]* shape so derived identifiers inherit it.
Example fix
# before [storage.postgres.main] table_prefix = "2026-memories" # after [storage.postgres.main] table_prefix = "memories_2026"
Defensive patterns
Strategy: type-guard
Validate before calling
if !is_valid_pg_identifier(&name) {
anyhow::bail!("identifier '{name}' must match [A-Za-z_][A-Za-z0-9_]*");
} Type guard
fn is_valid_pg_identifier(name: &str) -> bool {
let mut chars = name.chars();
matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
&& !name.is_empty()
} Prevention
- Constrain agent and storage alias names to [A-Za-z_][A-Za-z0-9_]* at creation time so derived SQL identifiers are always valid.
- Add a config-lint regex check for schema/table fields before constructing the postgres store.
When it happens
Trigger: Schema or table names like "2026_archive" or "-prod", or identifiers derived from agent/storage aliases beginning with a digit (agents.7writer), passed to the postgres store constructor or validated_schema_identifier.
Common situations: Numeric-prefixed autogenerated names (counters, years); kebab or symbol-prefixed aliases reused verbatim as table/schema names; names pasted with leading punctuation.
Related errors
- {field_name} must not be empty
- {field_name} can only contain ASCII letters, numbers, and un
- google_workspace.allowed_operations[{i}].service contains in
- google_workspace.allowed_operations[{i}].resource contains i
- google_workspace.allowed_operations[{i}].sub_resource contai
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/6980963fc777a038.
Report an issue: GitHub.