I've found the problem, although I don't have a solution yet. In short, a disabled dist tells Distrostatus "NO" but it's goodbye message is "NA". I don't know if that's a bug, though. If I change that "NO" to "NA" all the tests still pass, but I don't know enough about the long range intent to know if that's a good change.
Here's the walkthrough. I know you guys know the code, but here it is anyway: In my CPAN.pm config, I set halt_on_failure to true. Of course, if I had just searched for the error message, I would have found it in Shell.pm: if ($CPAN::Config->{halt_on_failure} && CPAN::Distrostatus::something_has_just_failed() ) { $CPAN::Frontend->mywarn("Stopping: '$meth' failed for '$s'.\n"); CPAN::Queue->nullify_queue; last QITEM; } So, when Distroprefs gets a hit, the Distrostatus gets set to a failure because it has "NO" in the string. --------------CPAN::Distrostatus::new <-- CPAN::Distribution CPAN::Distribution::get 1677 $VAR1 = bless( { 'COMMANDID' => 0, 'TEXT' => 'NO Disabled via prefs file \'/Users/brian/Desktop/DistroFailure/distroprefs/skip_expect.yml\' doc 0', 'TIME' => 1259845689, 'FAILED' => 1 }, 'CPAN::Distrostatus' ); This comes from Distribution.pm's get(): if ($self->prefs->{disabled} && ! $self->{force_update}) { my $why = sprintf( "Disabled via prefs file '%s' doc %d", $self->{prefs_file}, $self->{prefs_file_doc}, ); push @e, $why; $self->{unwrapped} = CPAN::Distrostatus->new("NO $why"); $goodbye_message = "[disabled] -- NA $why"; # note: not intended to be persistent but at least visible # during this session } else { Now, once something fails, the class variable $CPAN::Distroprefs::something_has_failed_at becomes true and nothing ever resets it. When something else checks something_has_just_failed(), it gets the answer from a previous call to install(). So, there's the tricky part. If I call install() twice, I expect it to be different than a recursive call to install(): CPAN::Shell->install(); CPAN::Shell->install(); # should start fresh I could wrap that and reset stuff myself, but I'd rather see install() knowing when it's at the top and when it's recursive. I'm still looking through the code and seeing what is what.