zeroclaw-labs/zeroclaw · error
{field_name} can only contain ASCII letters, numbers, and un
Error message
{field_name} can only contain ASCII letters, numbers, and underscores; got '{value}' What it means
The final identifier rule: after the first character, every remaining character must be an ASCII letter, digit, or underscore. Anything else — spaces, dashes, dots, quotes, non-ASCII — is rejected because these identifiers are formatted directly into SQL statements and any other character would break the statement or enable injection through identifiers.
Source
Thrown at crates/zeroclaw-memory/src/postgres.rs:346
})?
}
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!(
" AND m.created_at >= ${first_placeholder}::TIMESTAMPTZ AND m.created_at <= ${}::TIMESTAMPTZ",
first_placeholder + 1
),
(true, false) => {View on GitHub (pinned to 88bb9c8533)
Solutions
- Use snake_case for schema/table identifiers: replace dashes and spaces with underscores.
- Sanitize derived identifiers — keep [A-Za-z0-9_] and substitute everything else with an underscore.
- Trim whitespace when taking identifiers from user input or config.
Example fix
// before: alias "my-agent" -> "my-agent_memories" (rejected)
let table = format!("{agent}_memories", agent = alias);
// after: sanitize to the allowed charset
let table = format!("{}_memories", sanitize_identifier(&alias)); // "my_agent_memories" Defensive patterns
Strategy: type-guard
Validate before calling
let table = sanitize_identifier(&format!("{alias}_memories"));
assert!(is_valid_pg_identifier(&table), "derived identifier failed validation"); Type guard
fn sanitize_identifier(raw: &str) -> String {
let mut s: String = raw
.chars()
.map(|c| if c.is_ascii_alphanumeric() || c == '_' { c } else { '_' })
.collect();
if !s.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
s.insert(0, '_');
}
s
} Prevention
- Never interpolate raw user-controlled strings into schema/table names; sanitize to [A-Za-z0-9_] first (this also closes identifier-based SQL injection).
- Prefer allowlisting identifiers against a fixed set when the set of legal names is known.
When it happens
Trigger: Identifiers containing dashes (kebab-case aliases like my-agent), dots, spaces, quotes, or Unicode characters, passed to new or validated_schema_identifier for the postgres backend.
Common situations: Kebab-case agent or storage alias names used directly as schema/table names; names with trailing whitespace from copy-paste; internationalized names with accented characters.
Related errors
- {field_name} must not be empty
- {field_name} must start with an ASCII letter or underscore;
- modal custom_id exceeds Discord's 100-char limit; cannot ope
- slash command registration failed for '{name}' ({status}): {
- unknown git channel provider `{other}` (supported: github, g
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/843e15d2c252a987.
Report an issue: GitHub.