diff --git a/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm b/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm
index 5bd41a278dd..aaa4c400525 100644
--- a/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm
+++ b/src/test/perl/PostgreSQL/Test/BackgroundPsql.pm
@@ -100,7 +100,7 @@ sub new
 	  "Forbidden caller of constructor: package: $package, file: $file:$line"
 	  unless $package->isa('PostgreSQL::Test::Cluster');
 
-	$psql->{timeout} = IPC::Run::timeout(
+	$psql->{timeout} = IPC::Run::timer(
 		defined($timeout)
 		? $timeout
 		: $PostgreSQL::Test::Utils::timeout_default);
@@ -154,12 +154,14 @@ sub wait_connect
 	# errors anyway, but that might be added later.)
 	#
 	# See query() for details about why/how the banner is used.
-	my $banner = "background_psql: ready";
-	my $banner_match = qr/(^|\n)$banner\r?\n/;
-	$self->{stdin} .= "\\echo $banner\n\\warn $banner\n";
+	my $part1 = "background_psql:";
+	my $part2 = "ready";
+	my $banner = "$part1 $part2";
+	my $banner_match = qr/$banner\r?\n/;
+	$self->{stdin} .= "\\set part1 $part1\n\\set part2 $part2\n\\echo :part1 :part2\n\\warn :part1 :part2\n";
 	$self->{run}->pump()
 	  until ($self->{stdout} =~ /$banner_match/
-		  && $self->{stderr} =~ /$banner\r?\n/)
+		  && $self->{stderr} =~ /$banner_match/)
 	  || $self->{timeout}->is_expired;
 
 	note "connect output:\n",
@@ -253,7 +255,9 @@ sub query
 	# Feed the query to psql's stdin, followed by \n (so psql processes the
 	# line), by a ; (so that psql issues the query, if it doesn't include a ;
 	# itself), and a separator echoed both with \echo and \warn, that we can
-	# wait on.
+	# wait on.  The separator is broken into two psql variables, so that we
+	# can't accidentally match to readline's echo of the commands rather than
+	# the command output.
 	#
 	# To avoid somehow confusing the separator from separately issued queries,
 	# and to make it easier to debug, we include a per-psql query counter in
@@ -264,22 +268,16 @@ sub query
 	# stderr (or vice versa), even if psql printed them in the opposite
 	# order. We therefore wait on both.
 	#
-	# We need to match for the newline, because we try to remove it below, and
-	# it's possible to consume just the input *without* the newline. In
-	# interactive psql we emit \r\n, so we need to allow for that. Also need
-	# to be careful that we don't e.g. match the echoed \echo command, rather
-	# than its output.
-	my $banner = "background_psql: QUERY_SEPARATOR $query_cnt:";
-	my $banner_match = qr/(^|\n)$banner\r?\n/;
-	$self->{stdin} .= "$query\n;\n\\echo $banner\n\\warn $banner\n";
-	pump_until(
-		$self->{run}, $self->{timeout},
-		\$self->{stdout}, qr/$banner_match/);
-	pump_until(
-		$self->{run}, $self->{timeout},
-		\$self->{stderr}, qr/$banner_match/);
-
-	die "psql query timed out" if $self->{timeout}->is_expired;
+	# In interactive psql we emit \r\n, so we need to allow for that.
+	my $part1 = "background_psql:";
+	my $part2 = "QUERY_SEPARATOR_$query_cnt:";
+	my $banner = "$part1 $part2";
+	my $banner_match = qr/$banner\r?\n/;
+	$self->{stdin} .= "$query\n;\n\\set part1 $part1\n\\set part2 $part2\n\\echo :part1 :part2\n\\warn :part1 :part2\n";
+	$self->{run}->pump()
+	  until ($self->{stdout} =~ /$banner_match/
+		  && $self->{stderr} =~ /$banner_match/)
+	  || $self->{timeout}->is_expired;
 
 	note "results query $query_cnt:\n",
 	  explain {
@@ -287,9 +285,12 @@ sub query
 		stderr => $self->{stderr},
 	  } unless !$params{verbose};
 
+	die "psql query timed out" if $self->{timeout}->is_expired;
+
 	# Remove banner from stdout and stderr, our caller doesn't care.  The
 	# first newline is optional, as there would not be one if consuming an
 	# empty query result.
+	$banner_match = qr/\r?\n?$banner\r?\n/;
 	$output = $self->{stdout};
 	$output =~ s/$banner_match//;
 	$self->{stderr} =~ s/$banner_match//;
