When somebody else’s code conflicts with yours, removing it is often more honest than painting over it with a filter. Being able to suppress one specific output of a parent theme without editing that theme is a real benefit of the hook design.
The snag is that remove_action() and remove_filter() throw nothing when they fail. They return false and carry on, which means “did not remove” is indistinguishable from “was never there”.
Three things must match exactly
Priority is what slips most often. When both sides use the default of 10 it simply works, so people stop passing it at all — and then a callback registered at 20 never comes off with a removal at 10. Check the number.
Timing is the next most common. You cannot remove something not yet registered. If the other side registers on init, your removal has to run at a later priority within init, or on wp_loaded.
add_action( 'after_setup_theme', function () {
$ok = remove_action( 'kalium_wrapper_start', 'kalium_page_heading_title_display', 20 );
// A false here means the name, the priority or the timing is wrong.
// Do not let the failure pass silently.
} );
The identifier depends on what you are removing
A plain function is its name as a string. A static method is [ 'Some_Class', 'method' ] or 'Some_Class::method'.
An instance method needs the same object. If you cannot get hold of the instance used at registration, you cannot remove it. A singleton with an accessor is fine; a class that registers itself in its constructor and keeps no public reference leaves you no way in.
Closures cannot be removed by name, because there is no handle to name. That is a consequence of the design rather than a defect, and it produces a rule for the author of a plugin: do not attach closures to hooks other people may reasonably need to adjust. Use a named function, or a method on an instance you expose. Keep closures for internal wiring nobody has any reason to touch.
The trap of registration happening inside a hook
You also have to notice which hook the registration happens inside. This repository hit that while stripping the parent theme’s front-end scripts: they were enqueued from within wp_footer, so trying to dequeue them at wp_head found nothing in the queue yet.
The fix was not to dequeue the script but to remove the action that enqueues it. Stepping back one level and asking “what exactly am I removing?” is the answer in cases like this.
Afterwards, check what else disappeared. The same callback may have been doing something you did not know about. How we verify that during real work is published step by step on our process page, with related writing in the development workflow archive.
Next part
So far this has been about consuming extension points other people left. The next part turns it around — leaving hooks in your own code so that others can change behaviour without editing your files.