jamiepine/voicebox · error · HTTPException

{error}

Error message

{error}

What it means

Returned by PUT /profiles/{profile_id}/effects when validate_effects_chain() rejects the supplied chain (HTTP 400). The handler serializes each effect via model_dump, then delegates to validate_effects_chain; only if that returns a non-empty error string does it raise, using the validator's message verbatim. It means the request body parsed as ProfileEffectsUpdate but the chain contents are semantically invalid.

Solutions

  1. Inspect the exact detail string from the 400 response — it comes straight from validate_effects_chain and names the offending field/effect.
  2. Cross-check the effect names and parameter keys against the current backend's supported effects list.
  3. Validate the chain client-side against the same schema before submitting.
  4. Ensure each effect object includes all required keys and that numeric params are within documented ranges.

Example fix

// before
await api.put(`/profiles/${id}/effects`, { effects_chain: rawChain });

// after
const errs = validateChainClient(rawChain); // mirror of validate_effects_chain
if (errs.length) throw new UserError(errs);
await api.put(`/profiles/${id}/effects`, { effects_chain: rawChain });
Defensive patterns

Strategy: validation

Validate before calling

function validateChainClient(chain) {
  const supported = new Set(['eq','compressor','reverb','gain']);
  const errs = [];
  for (const e of chain) {
    if (!supported.has(e.type)) errs.push(`unknown effect: ${e.type}`);
    if (typeof e.params?.gain === 'number' && (e.params.gain < 0 || e.params.gain > 4))
      errs.push(`gain out of range on ${e.type}`);
  }
  return errs;
}

Type guard

function isEffectsChain(v) { return Array.isArray(v) && v.every(e => e && typeof e === 'object' && typeof e.type === 'string'); }

Try / catch

try { await api.put(`/profiles/${id}/effects`, { effects_chain: chain }); }
catch (e) { if (e.response?.status === 400) showUser(e.response.data.detail); throw e; }

Prevention

When it happens

Trigger: Submitting an effects_chain with an unknown effect type, missing required parameters for an effect, out-of-range values, or a malformed effect object. Passing an empty object instead of a fully-formed effect descriptor.

Common situations: Frontend builds the chain from a stale list of supported effects after an engine upgrade that renamed/removed an effect. Copy-pasting a chain from docs for a different backend version. Negative or NaN gain values, unsupported sample-rate-dependent parameters.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/ce2a3540b4145032. Report an issue: GitHub.

Appendix: source

Thrown at backend/routes/profiles.py:354

@router.put("/profiles/{profile_id}/effects", response_model=models.VoiceProfileResponse)
async def update_profile_effects(
    profile_id: str,
    data: models.ProfileEffectsUpdate,
    db: Session = Depends(get_db),
):
    """Set or clear the default effects chain for a voice profile."""
    profile = db.query(DBVoiceProfile).filter_by(id=profile_id).first()
    if not profile:
        raise HTTPException(status_code=404, detail="Profile not found")

    if data.effects_chain is not None:
        from ..utils.effects import validate_effects_chain

        chain_dicts = [e.model_dump() for e in data.effects_chain]
        error = validate_effects_chain(chain_dicts)
        if error:
            raise HTTPException(status_code=400, detail=error)
        profile.effects_chain = _json.dumps(chain_dicts)
    else:
        profile.effects_chain = None

    profile.updated_at = datetime.utcnow()
    db.commit()
    db.refresh(profile)

    return _profile_to_response(profile)


# ── Personality endpoint ──────────────────────────────────────────────
# Only ``/profiles/{id}/compose`` remains — the UI's compose button
# produces a fresh in-character utterance the user can edit before
# speaking. Rewrite now happens inside ``/generate`` (and ``/speak``)
# when ``personality=true``; there is no standalone rewrite/respond/speak
# endpoint.

View on GitHub (pinned to 51f49dea19)