ZhuLinsen/daily_stock_analysis · error · ValueError
{field_name} must be at most {max_length} characters
Error message
{field_name} must be at most {max_length} characters What it means
ValueError from DecisionSignalService._optional_text (src/services/decision_signal_service.py:1199): optional free-text fields (notes, reasons, etc.) that are present and non-blank must not exceed max_length characters after stripping. Whitespace-only values are treated as absent (return None); anything longer than the cap raises '{field_name} must be at most {max_length} characters'. Each call site dictates the cap.
Source
Thrown at src/services/decision_signal_service.py:1199
if not text:
raise ValueError("trigger_source is required")
return text
@classmethod
def _normalize_optional_trigger_source(cls, value: Any) -> Optional[str]:
if value in (None, ""):
return None
return cls._normalize_trigger_source(value)
@staticmethod
def _optional_text(value: Any, field_name: str, *, max_length: int) -> Optional[str]:
if value is None:
return None
text = str(value).strip()
if not text:
return None
if len(text) > max_length:
raise ValueError(f"{field_name} must be at most {max_length} characters")
return text
@classmethod
def _optional_public_text(cls, value: Any, field_name: str, *, max_length: int) -> Optional[str]:
return cls._public_text(value, field_name, max_length=max_length, required=False)
@staticmethod
def _public_text(value: Any, field_name: str, *, max_length: int, required: bool) -> Optional[str]:
if value is None:
if required:
raise ValueError(f"{field_name} is required")
return None
text = sanitize_decision_signal_text(value)
if not text:
if required:
raise ValueError(f"{field_name} is required")
return None
if len(text) > max_length:View on GitHub (pinned to 5159bd72e8)
Solutions
- Truncate client-side before the call: text.strip()[:max_length] (check the error message or field schema for the exact cap).
- Move long-form content into a field designed for it (e.g. structured metadata/JSON signal_text) instead of a capped text column.
- Bind frontend inputs to the same maxlength as the backend schema.
- For machine-generated text, summarize or hash-and-store instead of inlining.
Example fix
# before
service.create_signal({..., "notes": full_analysis_paragraph}) # 5000 chars > cap → ValueError
# after
notes = full_analysis_paragraph.strip()[:500] or None
service.create_signal({..., "notes": notes}) Defensive patterns
Strategy: validation
Validate before calling
MAX = 500 # use the cap from the field schema / error message
text = payload.get('notes')
if text is not None:
text = str(text).strip() or None
payload['notes'] = text[:MAX] if text else None Type guard
def fits(text: str | None, max_length: int) -> bool:
return text is None or 0 < len(text.strip()) <= max_length Prevention
- Truncate long text at the ingestion boundary instead of relying on server rejection.
- Keep frontend maxlength synced with backend caps.
- Put long-form content in structured metadata fields, not capped text columns.
When it happens
Trigger: Sending a notes/reason string longer than the field's configured cap — e.g. pasting an entire LLM analysis paragraph into a short free-text field, or a full error traceback into a remarks field. Multi-byte content counts by character, not bytes, so CJK text hits the cap slower but still hits it.
Common situations: UI textarea without maxlength bound to a capped backend field; concatenating multiple reasons with '\n'.join() without trimming; logs forwarded into metadata text fields; changing the cap without notifying the frontend.
Related errors
- {field_name} must be an integer
- {field_name} must be positive
- unsupported_report_type
- terminal decision signal cannot be reactivated through statu
- score must be between 0 and 100
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/955ceb2025f63cdd.
Report an issue: GitHub.