zylon-ai/private-gpt · error · ValueError
First message in the block must be a USER message.
Error message
First message in the block must be a USER message.
What it means
Raised by DELETE /skills/{skill_id} when the skill exists but has readonly=True. Readonly skills are provisioned (e.g. built-in or admin-managed) and are protected from deletion; the route returns 403 Forbidden before calling delete_skill.
Source
Thrown at private_gpt/chat/input_models.py:719
for block in user_blocks:
if not block:
continue
# Find TLDR messages and their positions in this block
tldr_positions: list[int] = []
for j, message in enumerate(block):
if _has_tldr_content(message):
tldr_positions.append(j)
if not tldr_positions:
# No TLDR in this block, keep all messages as-is
add_by_id(block)
continue
user_message: ChatMessage = block[0]
if user_message and user_message.role != MessageRole.USER:
raise ValueError("First message in the block must be a USER message.")
# Group consecutive TLDR positions together so we can detect accumulated
# TLDRBlocks (where each subsequent block repeats all prior entries plus
# new ones at the end).
tldr_positions_grouped_by_consecutive = [
[num for _, num in group]
for _, group in groupby(
enumerate(tldr_positions), lambda x: x[1] - x[0]
)
]
# Tracks how many TLDR ChatMessages were emitted by the previous group.
# Accumulated right-side TLDRBlocks re-emit all prior summaries as a
# prefix, so we skip that prefix to avoid duplicates while still
# preserving distinct instances with identical text (e.g. TOOL("-")).
prev_group_tldr_count = 0
for i, pos in enumerate(tldr_positions_grouped_by_consecutive):View on GitHub (pinned to 4a030776a3)
Solutions
- Do not expose delete actions for skills where the API reports readonly=true.
- If removal is genuinely required, ask an operator to remove/flag the skill at the storage/admin level rather than the API.
- Check the skill metadata first (GET) and branch on skill.readonly.
Example fix
// before
await skillsApi.delete(skill.id, collection);
// after
if (skill.readonly) { toast('Readonly skill cannot be deleted'); return; }
await skillsApi.delete(skill.id, collection); Defensive patterns
Strategy: type-guard
Validate before calling
const skill = await skillsApi.get(id, collection);
if (skill?.readonly) { throw new ReadOnlySkillError(id); } Type guard
const isDeletableSkill = (s) => s != null && !s.readonly;
Try / catch
try { await skillsApi.delete(id, collection); }
catch (e) {
if (e.status === 403 && /readonly/.test(e.detail)) { /* hide delete UI */ }
else throw e;
} Prevention
- Read the readonly flag from skill metadata before enabling destructive actions
- Filter readonly skills out of bulk operations
- For readonly content you must change, clone it under a new id first
When it happens
Trigger: Attempting to delete a builtin/system skill flagged readonly in its collection; a UI that shows readonly skills with an active delete button.
Common situations: Trying to remove bundled skills shipped with the deployment; permission model changes that marked existing skills readonly after an upgrade.
Related errors
- Image size {image_size} exceeds maximum allowed size of {set
- Unsupported content block type: {type(block)}
- Invalid message order: expected {expected_roles} after {prev
- Unknown message role {self.role}. Expected 'system', 'user',
- Audio size {audio_size} exceeds maximum allowed size of {set
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/eb91e50d195f73df.
Report an issue: GitHub.