I don't have a solution, only a workaround: ``` undoCellContext[expr_] := Module[{ccSymbols, rules}, ccSymbols = Cases[expr, _Symbol, \[Infinity]] // DeleteDuplicates // Select[StringStartsQ["$CellContext`"]@*ToString]; rules = (# -> Symbol["Global`"<>SymbolName[#]]) & /@ ccSymbols; expr /. rules ] Once[...,"Notebook"] // undoCellContext ``` As an optimization, when `LeafCount[expr]` is suitably large, `undoCellContext` could switch from building a list and deleting duplicates to populating a HashSet within the delayed rule given to Cases. It would also perhaps help if `undoCellContext` held the expression, and the replacement symbols, unevaluated until it was done with it. We could "Fix" Once on versions where it's misbehavinbg: ``` If[$VersionNumber<=14., Unprotect[Once]; Once`Private`$overridden = True; Once[expr__,"Notebook"] := Block[{Once`Private`$overridden = False}, Once[expr, "Notebook"] // undoCellContext ] /; Once`Private`$overridden Protect[Once]; ] ``` For reference, `Once` is not guilty. The underlying problem is that `PersistentObject[...,"Notebook"]["Value"]` converts <code>Global`</code> symbols to <code>$CellContext`</code> on readout.