It's to do with when TeX swallows spaces. There's actually two parts to the "when". The first thing to know is that TeX swallows spaces when it reads the input. The second thing to know is that TeX swallows spaces after a command name. Putting those two together, we see that TeX swallows a space when it follows a command name in the input but not when already-read code is reinserted in to the stream. Let's now examine the two cases:
\zap@space#1 \@empty When TeX reads this, it doesn't know that #1 is going to be a command. For all it knows, #1 could be a pile of fetid dingo's kidneys. So it doesn't swallow the space. Thus when this code is executed, TeX sees: \zap@space\CurrentOption \@empty with the space still there. Hence, everyone is happy.
\zap@space\CurrentOption \@empty When TeX reads this, it knows that \CurrentOption is a command, so it swallows the space. Thus the actual code that the processor sees is \zap@space\CurrentOption\@empty and there's no space to zap, so \zap@space complains.
There are probably many solutions. A simple one is to ensure that when TeX sees that space then it isn't in "space swallowing" mode. To do this, we have to figure out a way to make it so that the last thing that TeX saw before the space isn't a command. A common way to do this is to put an empty group there: \zap@space\CurrentOption{} \@empty works.
(Edit:) As Philippe points out, the above adds the brace to the argument of \my@extraoptions. Here's one that doesn't, with fewer \expandafters:
\expandafter\zap@space\expandafter\CurrentOption\space\@empty
but in fact, looking at the output of \my@extraoptions then I think that there is something wrong with your \@ifundefined bit. Here's what I think you are after:
\ProvidesPackage{test}
\DeclareOption*{%
\edef\@temp{%
\xdef\noexpand\my@extraoptions{%
\@ifundefined{my@extraoptions}{}{\my@extraoptions,}%
\noexpand\zap@space\CurrentOption\space\noexpand\@empty}}
\@temp
}
\ProcessOptions
\show\my@extraoptions
with a calling program:
\documentclass{article}
\usepackage[hello,world]{test}
\begin{document}
hello
\end{document}
In the log file, I get that \my@extraoptions is hello,world. With your original code, I got ,hello,world - that is, with an initial comma.
As well as correcting the \@ifundefined, I've taken the alternative route through expansion. Rather than an excess of \expandafters, I've used an \edef to define a temporary macro which expands stuff as necessary (with a few expansions inhibited with \noexpand) which is then called straight after to do the actual definition.
(This is a trick I learnt here ... When to use \edef, \noexpand, and \expandafter? ... after seeing a macro with about 20 \expandafters be condensed to just one. That's also where I was accused of being a Cargo Cult Programmer!)