Theory of why this doesn't really work?

No that's fine - so long as 13 is big enough for the string plus its null terminator.

You could also pass in the address like this:

void test1(char * res, unsigned long a)
{
  sprintf(res,"<%lu>", a);
}


void loop ()
{
  char s[13];
  test1 (s, pow(2,32)) ;
  Serial.print (s) ;
}

Here s is local to loop(), but not to test1. loop() finishes using s before returning so it doesn't matter that its local to loop().