## Updated answer for WordPress 5.5.0+

As of 5.5.0, a new filter has been added to `wp_delete_attachment`:

```php

/**
 * Filters whether an attachment deletion should take place.
 *
 * @since 5.5.0
 *
 * @param bool|null $delete       Whether to go forward with deletion.
 * @param WP_Post   $post         Post object.
 * @param bool      $force_delete Whether to bypass the Trash.
 */
$check = apply_filters( 'pre_delete_attachment', null, $post, $force_delete );
if ( null !== $check ) {
	return $check;
}

```

This "short-circuit" filter is before _almost_ any meaningful action is made, save for trashing the attachment if certain conditions are met. A cleaner approach would now be:

```php

function action_maybe_delete( $id ) { 
  echo "Let's not delete Attachment id: " . $id ;
  // prevent the attachment from actually being deleted
  if( 1 == 1 ) { // made up condition 
    return false; 
  }
};
// add the action 
add_action( 'pre_delete_attachment', 'action_maybe_delete', 10, 1 ); 
```

- Returning anything other than `null` from this will prevent `wp_delete_attachment` from going any further, and instead the result of the filter will be returned.

--- 

## Old Answer from pre 5.5.0

First, let's take a look at where `delete_attachment` is fired in core. This happens on line `5062` of `wp-includes/post.php` in the [wp_delete_attachment](https://developer.wordpress.org/reference/functions/wp_delete_attachment/) function. Here's the relevant snippet:

    /**
     * Fires before an attachment is deleted, at the start of wp_delete_attachment().
     *
     * @since 2.0.0
     *
     * @param int $post_id Attachment ID.
     */
    do_action( 'delete_attachment', $post_id );
 
    wp_delete_object_term_relationships($post_id, array('category', 'post_tag'));
    wp_delete_object_term_relationships($post_id, get_object_taxonomies($post->post_type));
 
We can see that WordPress core is calling `do_action` - this method does not care what's returned; unlike `apply_filters`, `do_action` simply fires a hook and then processing continue despite any return values. Looking further at the source of `wp_delete_attachment`, there doesn't appear to be a way to "short-circuit" this process and prevent deleting the attachment.

**Except**, technically, you could use PHP's `die` or `exit` statements to end processing of the script, which _should_ effectively prevent the file from being deleted.

	function action_maybe_delete( $id ) {
		echo "Let's not delete Attachment id: " . $id ;
		// prevent the attachment from actually being deleted
		if( 1 == 1 ) { // made up condition
			die; // Prevent the script from continuing.
		}
	};
	// add the action
	add_action( 'delete_attachment', 'action_maybe_delete', 10, 1 ); ?php