{"record":{"id":"89255afe46932c5d","repo":"Shopify/liquid","slug":"contexterror-no-message","errorCode":null,"errorMessage":"ContextError (no message)","messagePattern":"ContextError \\(no message\\)","errorType":"exception","errorClass":"Liquid::ContextError","httpStatus":null,"severity":"error","filePath":"lib/liquid/context.rb","lineNumber":125,"sourceCode":"\n    def invoke(method, *args)\n      strainer.invoke(method, *args).to_liquid\n    end\n\n    # Push new local scope on the stack. use <tt>Context#stack</tt> instead\n    def push(new_scope = {})\n      @scopes.unshift(new_scope)\n      check_overflow\n    end\n\n    # Merge a hash of variables in the current local scope\n    def merge(new_scopes)\n      @scopes[0].merge!(new_scopes)\n    end\n\n    # Pop from the stack. use <tt>Context#stack</tt> instead\n    def pop\n      raise ContextError if @scopes.size == 1\n      @scopes.shift\n    end\n\n    # Pushes a new local scope on the stack, pops it at the end of the block\n    #\n    # Example:\n    #   context.stack do\n    #      context['var'] = 'hi'\n    #   end\n    #\n    #   context['var']  #=> nil\n    def stack(new_scope = {})\n      push(new_scope)\n      yield\n    ensure\n      pop\n    end\n","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/Shopify/liquid/blob/807d45a6b3d4568e64e86b375e3702df2c7c860c/lib/liquid/context.rb#L107-L143","documentation":"Liquid raises ContextError when Context#pop is called but only the single base scope remains on the scope stack — there is nothing to pop. pop is a low-level API; the comment says to use Context#stack instead, which guarantees balanced push/pop. Unbalanced calls to push (without a block) and pop trigger it.","triggerScenarios":"Calling context.pop without a prior context.push; calling pop twice for one push; calling pop inside Context#stack's block (stack already pops on exit); custom tags manipulating scopes manually and mis-balancing them.","commonSituations":"Custom tag authors writing direct scope-stack manipulation; rescuing from mid-block errors leaving pushes unbalanced; migrating old code that used the deprecated push/pop API; plugins written against very old Liquid internals.","solutions":["Use `context.stack { ... }` instead of manual push/pop — it restores the stack automatically even on exceptions.","Balance every manual push with exactly one pop (use ensure/begin-end).","Remove any pop call made inside a Context#stack block.","Refactor custom tags to use new_scope or stack helpers rather than raw scope manipulation."],"exampleFix":"// before\ncontext.push\nrender_part(context)\ncontext.pop\n// after\ncontext.stack do\n  render_part(context)\nend","handlingStrategy":"try-catch","validationCode":"# never call pop unless you pushed:\n# track depth yourself\ndef safe_pop(ctx)\n  ctx.pop if ctx.instance_variable_get(:@scopes).size > 1 rescue nil\nend","typeGuard":null,"tryCatchPattern":"begin\n  context.pop\nrescue Liquid::ContextError\n  logger.warn('context.pop on empty scope stack — use context.stack instead')\nend","preventionTips":["Always prefer context.stack { ... } over manual push/pop.","Wrap manual scope changes in begin/ensure so exceptions can't unbalance the stack.","Never pop inside a Context#stack block.","Audit custom tags for direct @scopes manipulation when upgrading Liquid."],"tags":["liquid","context","scope-stack","internal-error"],"backgroundTag":"invalid-state-transition","analyzedSha":"807d45a6b3d4568e64e86b375e3702df2c7c860c","analyzedAt":"2026-09-08T11:31:38.917Z","contentChangedAt":"2026-09-08T11:31:38.917Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}