{"record":{"id":"84544cc0ef83dfa2","repo":"BerriAI/litellm","slug":"augmented-assignment-op-r-is-not-supported","errorCode":null,"errorMessage":"augmented assignment {op!r} is not supported","messagePattern":"augmented assignment (.+?) is not supported","errorType":"validation","errorClass":"SyntaxError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/guardrails/guardrail_hooks/custom_code/sandbox.py","lineNumber":85,"sourceCode":"    \"%=\": operator.imod,\n    \"**=\": operator.ipow,\n    \"@=\": operator.imatmul,\n    \"&=\": operator.iand,\n    \"|=\": operator.ior,\n    \"^=\": operator.ixor,\n    \"<<=\": operator.ilshift,\n    \">>=\": operator.irshift,\n}\n\n\ndef _inplacevar_(op: str, x: Any, y: Any) -> Any:\n    # RestrictedPython rewrites ``x += 1`` on a simple name into\n    # ``x = _inplacevar_(\"+=\", x, 1)``. The package deliberately ships no\n    # default, so we dispatch through ``operator``'s in-place helpers, which\n    # honour Python's normal ``__iadd__``/``__add__`` fallback.\n    fn: Final = _INPLACE_OPS.get(op)\n    if fn is None:\n        raise SyntaxError(f\"augmented assignment {op!r} is not supported\")\n    return fn(x, y)\n\n\ndef _build_sandbox_builtins() -> dict[str, Any]:\n    # ``limited_builtins`` overrides ``list``/``tuple``/``range`` from\n    # ``safe_builtins`` with bounds-checking variants (e.g. ``limited_range``\n    # rejects ``range(10**18)``). ``utility_builtins`` adds ``set``,\n    # ``frozenset``, ``math``, ``random``, and a filtered ``string`` delegator.\n    return {\n        **safe_builtins,\n        **limited_builtins,\n        **utility_builtins,\n    }\n\n\ndef build_sandbox_globals() -> dict[str, Any]:\n    \"\"\"Assemble the globals dict for executing guardrail code.\n","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/guardrails/guardrail_hooks/custom_code/sandbox.py#L67-L103","documentation":"SyntaxError raised by the sandbox's _inplacevar_ helper when RestrictedPython rewrites an augmented assignment (x += 1) into _inplacevar_('+=', x, 1) and the operator string is not in the supported table. The shipped _INPLACE_OPS table maps all 13 Python augmented operators, so in practice this branch is defensive: reaching it means the installed RestrictedPython emitted an operator token this litellm sandbox version does not map (dependency drift), not that '+=' itself is banned.","triggerScenarios":"Custom code uses an augmented assignment whose rewritten op string misses the table — only plausible with a mismatched/newer RestrictedPython emitting an unexpected token; standard +=, -=, @=, <<= etc. all work. Surfacing at compile/exec time it wraps into 'Syntax error in custom code', and inside a running function it surfaces as CustomCodeExecutionError.","commonSituations":"RestrictedPython upgraded independently of litellm (pip resolver drift); base image rebuilt with newer transitive dependencies; error message quotes an operator the sandbox does not recognize.","solutions":["Pin RestrictedPython to the version the installed litellm was tested with","As a workaround, rewrite the augmented assignment as explicit rebinding: x = x + y","Upgrade or downgrade litellm so sandbox.py matches the installed RestrictedPython","Report the op string shown in the message — it identifies the unmapped operator"],"exampleFix":"# before (custom_code, hits an unmapped op after dependency drift)\nscore @= weights\n\n# after: explicit rebinding\nscore = score @ weights","handlingStrategy":"validation","validationCode":"import RestrictedPython, litellm\nfrom litellm.proxy.guardrails.guardrail_hooks.custom_code.sandbox import (\n    build_sandbox_globals, compile_sandboxed,\n)\n\ndef sandbox_compile_check(src: str) -> None:\n    g = build_sandbox_globals()          # uses the same _inplacevar_ table\n    exec(compile_sandboxed(src), g)      # any unmapped augmented op raises here\n\nprint('RestrictedPython', RestrictedPython.__version__, '| litellm', litellm.__version__)","typeGuard":null,"tryCatchPattern":"from litellm.proxy.guardrails.guardrail_hooks.custom_code.custom_code_guardrail import CustomCodeCompilationError\ntry:\n    guardrail._compile_custom_code()\nexcept CustomCodeCompilationError as e:\n    if 'augmented assignment' in str(e):\n        # dependency drift: rewrite to explicit rebinding or pin RestrictedPython\n        log.error('sandbox/litellm version mismatch: %s', e)\n    raise","preventionTips":["Pin RestrictedPython (and litellm) versions in lockfiles so they upgrade together","Run the sandbox compile preflight in CI with the production dependency set","Prefer x = x + y style in generated guardrail code - it bypasses the _inplacevar_ path entirely"],"tags":["guardrails","custom-code","sandbox","restricted-python","syntax-error","dependency-drift"],"backgroundTag":"sandbox-restricted-syntax","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}