Try this:
(if: ((passage:)'s tags contains "TagA") and ((passage:)'s tags contains "TagB"))[...
Explanation:
The "and" operator joins two expressions together, however, these expressions need to both be complete. Having a complete expression is sort of like having a complete sentence: in a sentence you need to have all the parts, a subject and a predicate. In an expression, you need an operator, and a number of values (usually two, but sometimes one or three) to operate on based on what that operator is. In your first example, you have one complete expression, and then an "and", and then a value with no operator. In an (if:) the expression is a comparison, and each expression is being evaluated for whether it is true or false. So your expression get evaluated like this:
IF:
The passage's tags contains 'TagA' is true.
AND IF:
'TagB' is true.
THEN:
Do this hook.
The second instruction isn't complete, and doesn't make sense. It can't really be evaluated.
Note:
In Harlowe 2, the parser will attempt to figure out what you mean, but don't rely on it. Always use complete expressions.
Edit. Forgot a "contains" in the example.