Brute force passwords in C (CS50 exercise)
valid_args is a well-known anti-idiom:

    if (condition) {
        return true;
    }
    return false;


is a long way to say

    return condition;

crack always return an empty string "". It is as good as returning nothing.
check terminates the program on success. This seriously limits its utility: the business logic doesn't belong to such depth of the call tree. Consider returning a boolean instead.
I do not endorse recursion when an iterative approach suffices. Try to express a power set generation iteratively (hint: think of base-52 numbers).
From what we can see, it is impossible to tell whether crypt returns a static buffer, or allocates a new one for each call. Consider documenting that.
I happen to know that &lt;cs50.h&gt; defines string as an alias to char *. I don't think it is a good idea to begin with; in any case you should be consistent: brute_force passes salt and hash as string, while check receives them as char *.