diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d4202b2d60d3b8d856959977a370adc5f8e87c7..f7a79c72b0bfda539f125b660160d7fb8298141d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -200,6 +200,9 @@ HeapLang, which is now in a separate package `coq-iris-heap-lang`.
   own file [heap_lang.class_instances](iris_heap_lang/class_instances.v).
 * Move `inv_head_step` tactic and `head_step` auto hints (now part of new hint
   database `head_step`) to [heap_lang.tactics](iris_heap_lang/tactics.v).
+* The tactic `wp_apply` no longer performs `wp_pures` before applying the given
+  lemma. The new tactic `wp_smart_apply` repeatedly performs single `wp_pure`
+  steps until the lemma matches the goal.
 
 The following `sed` script helps adjust your code to the renaming (on macOS,
 replace `sed` by `gsed`, installed via e.g. `brew install gnu-sed`).
diff --git a/iris_heap_lang/lib/array.v b/iris_heap_lang/lib/array.v
index 94a7fc428df86b00308bea70bc87b43f1f8831c9..4a844ee4e3b8a9af5ea0b9c03482f9450cf04fc7 100644
--- a/iris_heap_lang/lib/array.v
+++ b/iris_heap_lang/lib/array.v
@@ -83,7 +83,7 @@ Section proof.
     iDestruct (array_cons with "Hdst") as "[Hv1 Hdst]".
     iDestruct (array_cons with "Hsrc") as "[Hv2 Hsrc]".
     wp_load; wp_store.
-    wp_apply ("IH" with "[%] [%] Hdst Hsrc"); [ lia .. | ].
+    wp_smart_apply ("IH" with "[%] [%] Hdst Hsrc"); [ lia .. | ].
     iIntros "[Hvdst Hvsrc]".
     iApply "HΦ"; by iFrame.
   Qed.
@@ -107,7 +107,7 @@ Section proof.
     iIntros (Hvl Hn Φ) "Hvl HΦ".
     wp_lam.
     wp_alloc dst as "Hdst"; first by auto.
-    wp_apply (twp_array_copy_to with "[$Hdst $Hvl]").
+    wp_smart_apply (twp_array_copy_to with "[$Hdst $Hvl]").
     - rewrite replicate_length Z2Nat.id; lia.
     - auto.
     - iIntros "[Hdst Hl]".
@@ -147,7 +147,7 @@ Section proof.
       rewrite bool_decide_eq_false_2; last naive_solver lia.
       iDestruct (array_cons with "Hl") as "[Hl HSl]".
       iDestruct "Hf" as "[Hf HSf]".
-      wp_apply (wp_wand with "Hf"); iIntros (v) "Hv". wp_store. wp_pures.
+      wp_smart_apply (wp_wand with "Hf"); iIntros (v) "Hv". wp_store. wp_pures.
       rewrite Z.add_1_r -Nat2Z.inj_succ.
       iApply ("IH" with "[%] [HSl] HSf"); first lia.
       { by rewrite loc_add_assoc Z.add_1_r -Nat2Z.inj_succ. }
@@ -174,7 +174,7 @@ Section proof.
       rewrite bool_decide_eq_false_2; last naive_solver lia.
       iDestruct (array_cons with "Hl") as "[Hl HSl]".
       iDestruct "Hf" as "[Hf HSf]".
-      wp_apply (twp_wand with "Hf"); iIntros (v) "Hv". wp_store. wp_pures.
+      wp_smart_apply (twp_wand with "Hf"); iIntros (v) "Hv". wp_store. wp_pures.
       rewrite Z.add_1_r -Nat2Z.inj_succ.
       iApply ("IH" with "[%] [HSl] HSf"); first lia.
       { by rewrite loc_add_assoc Z.add_1_r -Nat2Z.inj_succ. }
@@ -195,7 +195,7 @@ Section proof.
       }}}.
     Proof.
       iIntros (Hn Φ) "Hf HΦ". wp_lam. wp_alloc l as "Hl"; first done.
-      wp_apply (wp_array_init_loop _ _ _ 0 n (Z.to_nat n) with "[Hl $Hf] [HΦ]").
+      wp_smart_apply (wp_array_init_loop _ _ _ 0 n (Z.to_nat n) with "[Hl $Hf] [HΦ]").
       { by rewrite /= Z2Nat.id; last lia. }
       { by rewrite loc_add_0. }
       iIntros "!>" (vs). iDestruct 1 as (Hlen) "[Hl Hvs]". wp_pures.
@@ -214,7 +214,7 @@ Section proof.
       }]].
     Proof.
       iIntros (Hn Φ) "Hf HΦ". wp_lam. wp_alloc l as "Hl"; first done.
-      wp_apply (twp_array_init_loop _ _ _ 0 n (Z.to_nat n) with "[Hl $Hf] [HΦ]").
+      wp_smart_apply (twp_array_init_loop _ _ _ 0 n (Z.to_nat n) with "[Hl $Hf] [HΦ]").
       { by rewrite /= Z2Nat.id; last lia. }
       { by rewrite loc_add_0. }
       iIntros (vs). iDestruct 1 as (Hlen) "[Hl Hvs]". wp_pures.
diff --git a/iris_heap_lang/lib/assert.v b/iris_heap_lang/lib/assert.v
index 1397a3380b8fdd0c53c76a44ddaf63f2cfc8ed87..160377bc09a0be2b48da050d87ae30aaad57c853 100644
--- a/iris_heap_lang/lib/assert.v
+++ b/iris_heap_lang/lib/assert.v
@@ -15,7 +15,7 @@ Lemma twp_assert `{!heapG Σ} E (Φ : val → iProp Σ) e :
   WP (assert: e)%V @ E [{ Φ }].
 Proof.
   iIntros "HΦ". wp_lam.
-  wp_apply (twp_wand with "HΦ"). iIntros (v) "[% ?]"; subst. by wp_if.
+  wp_smart_apply (twp_wand with "HΦ"). iIntros (v) "[% ?]"; subst. by wp_if.
 Qed.
 
 Lemma wp_assert `{!heapG Σ} E (Φ : val → iProp Σ) e :
@@ -23,5 +23,5 @@ Lemma wp_assert `{!heapG Σ} E (Φ : val → iProp Σ) e :
   WP (assert: e)%V @ E {{ Φ }}.
 Proof.
   iIntros "HΦ". wp_lam.
-  wp_apply (wp_wand with "HΦ"). iIntros (v) "[% ?]"; subst. by wp_if.
+  wp_smart_apply (wp_wand with "HΦ"). iIntros (v) "[% ?]"; subst. by wp_if.
 Qed.
diff --git a/iris_heap_lang/lib/increment.v b/iris_heap_lang/lib/increment.v
index 174257263f0237b8275311182e200cb766b43447..f670ca044a3c74e63fea9c09590315e25a3f4f71 100644
--- a/iris_heap_lang/lib/increment.v
+++ b/iris_heap_lang/lib/increment.v
@@ -63,7 +63,7 @@ Section increment.
     { (* abort case *) done. }
     iIntros "Hl". iMod ("Hclose" with "Hl") as "AU". iModIntro.
     (* Now go on *)
-    awp_apply cas_spec; first done.
+    wp_pures. awp_apply cas_spec; first done.
     (* Prove the atomic update for CAS *)
     rewrite /atomic_acc /=. iMod "AU" as (w) "[Hl Hclose]".
     iModIntro. iExists _. iFrame "Hl". iSplit.
@@ -87,7 +87,7 @@ Section increment.
     iIntros (x) "H↦". iAaccIntro with "H↦"; first by eauto with iFrame.
     iIntros "$ !> AU !>".
     (* Now go on *)
-    awp_apply cas_spec; first done.
+    wp_pures. awp_apply cas_spec; first done.
     (* Prove the atomic update for CAS *)
     iApply (aacc_aupd with "AU"); first done.
     iIntros (x') "H↦". iAaccIntro with "H↦"; first by eauto with iFrame.
@@ -118,7 +118,7 @@ Section increment.
   Proof.
     iIntros "Hl" (Φ) "AU". wp_lam.
     wp_apply (atomic_wp_seq $! (load_spec _) with "Hl").
-    iIntros "Hl". awp_apply store_spec.
+    iIntros "Hl". wp_pures. awp_apply store_spec.
     (* Prove the atomic update for store *)
     iApply (aacc_aupd_commit with "AU"); first done.
     iIntros (x) "H↦".
@@ -155,7 +155,7 @@ Section increment_client.
       (* The continuation: From after the atomic triple to the postcondition of the WP *)
       done.
     }
-    wp_apply wp_par.
+    wp_smart_apply wp_par.
     - iAssumption.
     - iAssumption.
     - iIntros (??) "_ !>". done.
diff --git a/iris_heap_lang/lib/spawn.v b/iris_heap_lang/lib/spawn.v
index 11c2ea34f8f232703d6951673942617006b8f11f..66fe28f78ac7e3ba653c749b90f0bae34dd217c3 100644
--- a/iris_heap_lang/lib/spawn.v
+++ b/iris_heap_lang/lib/spawn.v
@@ -52,7 +52,7 @@ Proof.
   iMod (own_alloc (Excl ())) as (γ) "Hγ"; first done.
   iMod (inv_alloc N _ (spawn_inv γ l Ψ) with "[Hl]") as "#?".
   { iNext. iExists NONEV. iFrame; eauto. }
-  wp_apply (wp_fork with "[Hf]").
+  wp_smart_apply (wp_fork with "[Hf]").
   - iNext. wp_bind (f _). iApply (wp_wand with "Hf"); iIntros (v) "Hv".
     wp_inj. iInv N as (v') "[Hl _]".
     wp_store. iSplitL; last done. iIntros "!> !>". iExists (SOMEV v). iFrame. eauto.
@@ -66,7 +66,7 @@ Proof.
   iLöb as "IH". wp_rec. wp_bind (! _)%E. iInv N as (v) "[Hl Hinv]".
   wp_load. iDestruct "Hinv" as "[%|Hinv]"; subst.
   - iModIntro. iSplitL "Hl"; [iNext; iExists _; iFrame; eauto|].
-    wp_apply ("IH" with "Hγ [HΦ]"). auto.
+    wp_smart_apply ("IH" with "Hγ [HΦ]"). auto.
   - iDestruct "Hinv" as (v' ->) "[HΨ|Hγ']".
     + iModIntro. iSplitL "Hl Hγ"; [iNext; iExists _; iFrame; eauto|].
       wp_pures. by iApply "HΦ".
diff --git a/iris_heap_lang/proofmode.v b/iris_heap_lang/proofmode.v
index a640e92d17bc69b6778cca2cbd460fc51807e5da..c74421133253dfec690def68200321cf7d801e5c 100644
--- a/iris_heap_lang/proofmode.v
+++ b/iris_heap_lang/proofmode.v
@@ -512,30 +512,40 @@ Proof.
 Qed.
 End heap.
 
-(** Evaluate [lem] to a hypothesis [H] that can be applied, and then run
-[wp_bind K; tac H] for every possible evaluation context.  [tac] can do
-[iApplyHyp H] to actually apply the hypothesis.  TC resolution of [lem] premises
-happens *after* [tac H] got executed. *)
-Tactic Notation "wp_apply_core" open_constr(lem) tactic3(tac) :=
-  wp_pures;
-  iPoseProofCore lem as false (fun H =>
-    lazymatch goal with
-    | |- envs_entails _ (wp ?s ?E ?e ?Q) =>
-      reshape_expr e ltac:(fun K e' =>
-        wp_bind_core K; tac H) ||
-      lazymatch iTypeOf H with
-      | Some (_,?P) => fail "wp_apply: cannot apply" P
-      end
-    | |- envs_entails _ (twp ?s ?E ?e ?Q) =>
-      reshape_expr e ltac:(fun K e' =>
-        twp_bind_core K; tac H) ||
-      lazymatch iTypeOf H with
-      | Some (_,?P) => fail "wp_apply: cannot apply" P
-      end
-    | _ => fail "wp_apply: not a 'wp'"
-    end).
+(** The tactic [wp_apply_core lem tac_suc tac_fail] evaluates [lem] to a
+hypothesis [H] that can be applied, and then runs [wp_bind_core K; tac_suc H]
+for every possible evaluation context [K].
+
+- The tactic [tac_suc] should do [iApplyHyp H] to actually apply the hypothesis,
+  but can perform other operations in addition (see [wp_apply] and [awp_apply]
+  below).
+- The tactic [tac_fail cont] is called when [tac_suc H] fails for all evaluation
+  contexts [K], and can perform further operations before invoking [cont] to
+  try again.
+
+TC resolution of [lem] premises happens *after* [tac_suc H] got executed. *)
+Ltac wp_apply_core lem tac_suc tac_fail := first
+  [iPoseProofCore lem as false (fun H =>
+     lazymatch goal with
+     | |- envs_entails _ (wp ?s ?E ?e ?Q) =>
+       reshape_expr e ltac:(fun K e' =>
+         wp_bind_core K; tac_suc H)
+     | |- envs_entails _ (twp ?s ?E ?e ?Q) =>
+       reshape_expr e ltac:(fun K e' =>
+         twp_bind_core K; tac_suc H)
+     | _ => fail 1 "wp_apply: not a 'wp'"
+     end)
+  |tac_fail ltac:(fun _ => wp_apply_core lem tac_suc tac_fail)
+  |let P := type of lem in
+   fail "wp_apply: cannot apply" lem ":" P ].
+
 Tactic Notation "wp_apply" open_constr(lem) :=
-  wp_apply_core lem (fun H => iApplyHyp H; try iNext; try wp_expr_simpl).
+  wp_apply_core lem ltac:(fun H => iApplyHyp H; try iNext; try wp_expr_simpl)
+                    ltac:(fun cont => fail).
+Tactic Notation "wp_smart_apply" open_constr(lem) :=
+  wp_apply_core lem ltac:(fun H => iApplyHyp H; try iNext; try wp_expr_simpl)
+                    ltac:(fun cont => wp_pure _; []; cont ()).
+
 (** Tactic tailored for atomic triples: the first, simple one just runs
 [iAuIntro] on the goal, as atomic triples always have an atomic update as their
 premise.  The second one additionaly does some framing: it gets rid of [Hs] from
@@ -543,10 +553,13 @@ the context, which is intended to be the non-laterable assertions that iAuIntro
 would choke on.  You get them all back in the continuation of the atomic
 operation. *)
 Tactic Notation "awp_apply" open_constr(lem) :=
-  wp_apply_core lem (fun H => iApplyHyp H);
+  wp_apply_core lem ltac:(fun H => iApplyHyp H) ltac:(fun cont => fail);
   last iAuIntro.
 Tactic Notation "awp_apply" open_constr(lem) "without" constr(Hs) :=
-  wp_apply_core lem (fun H => iApply wp_frame_wand_l; iSplitL Hs; [iAccu|iApplyHyp H]);
+  wp_apply_core lem
+    ltac:(fun H =>
+      iApply wp_frame_wand_l; iSplitL Hs; [iAccu|iApplyHyp H]) 
+    ltac:(fun cont => fail);
   last iAuIntro.
 
 Tactic Notation "wp_alloc" ident(l) "as" constr(H) :=
diff --git a/tests/heap_lang.v b/tests/heap_lang.v
index 2401114aaba70d51630b67c1c45eb2ff427b0205..9b9310bf17b523d0d968d7e4bc694b4e731b3b55 100644
--- a/tests/heap_lang.v
+++ b/tests/heap_lang.v
@@ -115,14 +115,14 @@ Section tests.
   Proof.
     iIntros "HΦ". wp_lam.
     wp_op. case_bool_decide.
-    - wp_apply FindPred_spec; first lia. wp_pures.
+    - wp_smart_apply FindPred_spec; first lia. wp_pures.
       by replace (n - 1)%Z with (- (-n + 2 - 1))%Z by lia.
-    - wp_apply FindPred_spec; eauto with lia.
+    - wp_smart_apply FindPred_spec; eauto with lia.
   Qed.
 
   Lemma Pred_user E :
     ⊢ WP let: "x" := Pred #42 in Pred "x" @ E [{ v, ⌜v = #40⌝ }].
-  Proof. iIntros "". wp_apply Pred_spec. by wp_apply Pred_spec. Qed.
+  Proof. iIntros "". wp_apply Pred_spec. by wp_smart_apply Pred_spec. Qed.
 
   Definition Id : val :=
     rec: "go" "x" :=
diff --git a/tests/heap_lang_proph.v b/tests/heap_lang_proph.v
index cab2dda3ac8d7e31e01ea5cf0818cffb2a823092..8b59a127df2d145161968b84dd01dd9eb7439511 100644
--- a/tests/heap_lang_proph.v
+++ b/tests/heap_lang_proph.v
@@ -47,7 +47,7 @@ Section tests.
       Resolve (Resolve (#n - #n) ((λ: "x", "x") #p2) (#0 + #2)) ((λ: "x", "x") #p1) (#0 + #1) @ s; E
     {{{ RET #0 ; ∃ vs1' vs2', ⌜vs1 = (#0, #1)::vs1' ∧ vs2 = (#0, #2)::vs2'⌝ ∗ proph p1 vs1' ∗ proph p2 vs2' }}}.
   Proof.
-    iIntros (Φ) "[Hp1 Hp2] HΦ".
+    iIntros (Φ) "[Hp1 Hp2] HΦ". wp_pures.
     wp_apply (wp_resolve with "Hp1"); first done.
     wp_apply (wp_resolve with "Hp2"); first done.
     wp_op.
diff --git a/tests/one_shot.v b/tests/one_shot.v
index 1c2ce33d568233dbe255dc14580ab516cf1393d2..b6dd81209482ff9714b715bbe886fdb9f613be0c 100644
--- a/tests/one_shot.v
+++ b/tests/one_shot.v
@@ -84,7 +84,7 @@ Proof.
     iDestruct (own_valid_2 with "Hγ Hγ'") as %?%to_agree_op_inv_L; subst.
     iModIntro. iSplitL "Hl".
     { iNext; iRight; by eauto. }
-    wp_apply wp_assert. wp_pures. by case_bool_decide.
+    wp_smart_apply wp_assert. wp_pures. by case_bool_decide.
 Qed.
 
 Lemma ht_one_shot (Φ : val → iProp Σ) :
@@ -95,8 +95,8 @@ Lemma ht_one_shot (Φ : val → iProp Σ) :
     }}.
 Proof.
   iIntros "!> _". iApply wp_one_shot. iIntros (f1 f2) "[#Hf1 #Hf2]"; iSplit.
-  - iIntros (n) "!> _". wp_apply "Hf1".
-  - iIntros "!> _". wp_apply (wp_wand with "Hf2"). by iIntros (v) "#? !> _".
+  - iIntros (n) "!> _". wp_smart_apply "Hf1".
+  - iIntros "!> _". wp_smart_apply (wp_wand with "Hf2"). by iIntros (v) "#? !> _".
 Qed.
 End proof.
 
@@ -111,8 +111,8 @@ Section client.
   Lemma client_safe : ⊢ WP client {{ _, True }}.
   Proof using Type*.
     rewrite /client. wp_apply wp_one_shot. iIntros (f1 f2) "[#Hf1 #Hf2]".
-    wp_let. wp_apply wp_par.
-    - wp_apply "Hf1".
+    wp_let. wp_smart_apply wp_par.
+    - wp_smart_apply "Hf1".
     - wp_proj. wp_bind (f2 _)%E. iApply wp_wand; first by iExact "Hf2".
       iIntros (check) "Hcheck". wp_pures. iApply "Hcheck".
     - auto.
diff --git a/tests/one_shot_once.v b/tests/one_shot_once.v
index 89c8cc4c41d86c47dc0fb1cbc6c171449dc4eaf7..e51a877c192fee7dd1d4cc724783a616b057efe5 100644
--- a/tests/one_shot_once.v
+++ b/tests/one_shot_once.v
@@ -113,8 +113,8 @@ Lemma ht_one_shot (Φ : val → iProp Σ) :
 Proof.
   iIntros "!> _". iApply wp_one_shot. iIntros (f1 f2 T) "(HT & #Hf1 & #Hf2)".
   iExists T. iFrame "HT". iSplit.
-  - iIntros (n) "!> HT". wp_apply "Hf1". done.
-  - iIntros "!> _". wp_apply (wp_wand with "Hf2"). by iIntros (v) "#? !> _".
+  - iIntros (n) "!> HT". wp_smart_apply "Hf1". done.
+  - iIntros "!> _". wp_smart_apply (wp_wand with "Hf2"). by iIntros (v) "#? !> _".
 Qed.
 End proof.
 
@@ -129,8 +129,8 @@ Section client.
   Lemma client_safe : ⊢ WP client {{ _, True }}.
   Proof using Type*.
     rewrite /client. wp_apply wp_one_shot. iIntros (f1 f2 T) "(HT & #Hf1 & #Hf2)".
-    wp_let. wp_apply (wp_par with "[HT]").
-    - wp_apply "Hf1". done.
+    wp_let. wp_smart_apply (wp_par with "[HT]").
+    - wp_smart_apply "Hf1". done.
     - wp_proj. wp_bind (f2 _)%E. iApply wp_wand; first by iExact "Hf2".
       iIntros (check) "Hcheck". wp_pures. iApply "Hcheck".
     - auto.
diff --git a/tests/tree_sum.v b/tests/tree_sum.v
index 221e86d15cca55c51309d6eaece7a25ba935b1f9..b68e2e1a3828fbb98510ceafb16ffdd227345009 100644
--- a/tests/tree_sum.v
+++ b/tests/tree_sum.v
@@ -57,7 +57,7 @@ Lemma sum_wp `{!heapG Σ} v t :
 Proof.
   iIntros (Φ) "Ht HΦ". rewrite /sum' /=.
   wp_lam. wp_alloc l as "Hl".
-  wp_apply (sum_loop_wp with "[$Hl $Ht]").
+  wp_smart_apply (sum_loop_wp with "[$Hl $Ht]").
   rewrite Z.add_0_r.
   iIntros "[Hl Ht]". wp_load. by iApply "HΦ".
 Qed.