This has been on my mind a long time -- this thread began in Dec '18. (I've archived the failed attempts at solutions)
I'm one of those people who tries to write generic code and frets about what would happen on the day that unset variables or ACTION!s come along. So I'm caught between the balance of feeling negligent by not peppering with checks, or junking up otherwise elegant code for the sake of something that could happen.
And It's Finally Solved! 
It's solved through lifted variables.
Lifted variables are those which store meta-representations of what they are assigned. If you do a lifted assignment, and a lifted retrieval, you can round-trip anything:
>> ^anything: 1020
== 1020
>> anything
== '1020
>> ^anything
== 1020
>> ^anything: ~
== ~ ; antiform (trash)
>> anything
== ~
>> ^anything
== ~ ; antiform (trash)
Given this property, it makes them an ideal insulator against the active dispatch of an action.
>> ^anything: append/
== ~#[frame! [series value :part :dup :line]]~ ; anti
>> anything
== ~#[frame! [series value :part :dup :line]]~
>> ^anything
== ~#[frame! [series value :part :dup :line]]~ ; anti
So quite simply, a FOR-EACH variable which is not lifted won't accept actions.
>> obj: make object! [field: does [print "I'm an action"]]
>> for-each [key val] obj [probe val]
** Error: Can't assign VAL antiform ACTION! in FOR-EACH, use ^VAL
>> obj: make object! [field: does [print "I'm an action"]]
>> for-each [key ^val] obj [probe ^val]
~#[frame! []]~ ; anti
Lifting lets you receive trash as well. And what's great here is that if you leave off the lift, it will work fine so long as you don't hit anything that needs to be lifted. Your default undecorated code is "safe". It's only when you enumerate actions and trash that you get notified that lifting is needed.
Quite the perfect solution. Case closed.