Bobby's Booby-Trapped Safe
Mathematica, 125 bytes (121 chars)

(f@{_}=0;f@l_:=1+Min[Max[{g=GatherBy[l,vFreeQ[#-v,0]]}~With~If[Length@g&lt;2,∞,f/@g]]&amp;/@R~T~#];f@(T=Tuples)[R=Range@10,#])&amp;


Try it online!

(it actually does not run the code, just the function pasted into TIO. So because the function is not invoked, it does nothing)

This cannot be run even with input n=1 because it has no memoization or whatever optimization. The ungolfed code below, however, does return 9 for n=1.

Ungolfed code:

n = Input[];

base = 10;

Clear@f;
f@{_} = 0;
f@l_ := f@l =
  1 + Min[
    Max[
       With[{groups = GatherBy[l, v \[Function] FreeQ[# - v, 0]]},
        If[Length@groups == 1, Infinity, f /@ groups]
        ]
       ] &amp; /@ Tuples[Range@base, n]
    ]

f@Tuples[Range@base, n]


This code run in upper bound time O(2^(10^n poly(n))), which is impractical for all n ≥ 2.

Explanation:

Let a number be represented as a list of n numbers from 1 to 10. There are 10^n numbers.
Then, the function f receive a list of "numbers" l (so a 2D list of digits), and return the information "assuming all the possible candidate numbers are in the list l, at least how many question are necessary to reduce it to 1 candidate in the worst case?"
So the function f is defined as:

f@{_} = 0;


When there is only 1 candidate, we only need to ask 0 more questions.

f@l_ := f@l =


Memoization (in ungolfed code) to slightly improve performance.

1 + 


Ask one question.

/@ Tuples[Range@base, n]


For each possible number (numbers are represent as n-tuple of digits, each digit is in the range from 1 to base (10)) to be asked,

Min[]


find the minimum (best strategy)

groups = GatherBy[l, v \[Function] FreeQ[# - v, 0]]


partition the candidates to 2 groups: one gives the value "Close" when ask this number, and the other gives the value "Fail" when ask this number.

If[Length@groups == 1,


If all the numbers in the list l get to the same answer when test,

Infinity


return Infinity (avoid asking this question, avoid loop forever)

, f /@ groups


otherwise recursively apply the function f to the resulting group

Max[]


and get the maximum (worst case).