Describe the bug
When attempting to remove a batch of tag changes made by an anonymous user from an images' tag change history, the request ends with a HTTP 500.
With debugging turned on, the issue at hand is a "FunctionClauseError". More in details.
Steps to reproduce the behavior:
- As an anonymous user, make a tag change on an image (Does not matter if adding/removing, or how many)
- As an administrator, go to the just modified image. Display the tag changes history, and click on the "Delete from history" button of the tag changes batch made in step 1
- Site returns a HTTP 500.
Expected behavior
The tag changes batch should simply be removed from history.
Screenshots
https://i.imgur.com/q3vN5AR.png
Desktop (please complete the following information):
- OS: Win 10
- Browser: Firefox 139.0.4 (64-bit)
- Version (I presume philomena?): 1.2.0
Additional context
FunctionClauseError at DELETE /images/1/tag_changes/12
Exception:
** (FunctionClauseError) no function clause matching in PhilomenaWeb.Image.TagChangeController.tag_change_details/1
(philomena 1.2.1) lib/philomena_web/controllers/image/tag_change_controller.ex:57: PhilomenaWeb.Image.TagChangeController.tag_change_details(%Philomena.TagChanges.TagChange{__meta__: #Ecto.Schema.Metadata<:loaded, "tag_changes">, id: 12, user_id: nil, user: nil, image_id: 1, image: #Ecto.Association.NotLoaded<association :image is not loaded>, tags: [%Philomena.TagChanges.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tag_change_tags">, tag_change_id: 12, tag_change: #Ecto.Association.NotLoaded<association :tag_change is not loaded>, tag_id: 182, tag: %Philomena.Tags.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 182, aliased_tag_id: nil, aliased_tag: #Ecto.Association.NotLoaded<association :aliased_tag is not loaded>, aliases: #Ecto.Association.NotLoaded<association :aliases is not loaded>, channels: #Ecto.Association.NotLoaded<association :channels is not loaded>, implied_tags: #Ecto.Association.NotLoaded<association :implied_tags is not loaded>, implied_by_tags: #Ecto.Association.NotLoaded<association :implied_by_tags is not loaded>, verified_links: #Ecto.Association.NotLoaded<association :verified_links is not loaded>, public_links: #Ecto.Association.NotLoaded<association :public_links is not loaded>, hidden_links: #Ecto.Association.NotLoaded<association :hidden_links is not loaded>, dnp_entries: #Ecto.Association.NotLoaded<association :dnp_entries is not loaded>, slug: "meow", name: "meow", category: nil, images_count: 0, description: "", short_description: nil, namespace: nil, name_in_namespace: "meow", image: nil, image_format: nil, image_mime_type: nil, mod_notes: nil, uploaded_image: nil, removed_image: nil, implied_tag_list: nil, created_at: ~U[2025-06-16 18:57:37Z], updated_at: ~U[2025-06-16 18:57:37Z]}, added: true}], ip: %Postgrex.INET{address: {172, 18, 0, 7}, netmask: 32}, fingerprint: "d0547c7beb554b8", created_at: ~U[2025-06-16 19:12:04Z]})
(philomena 1.2.1) lib/philomena_web/controllers/image/tag_change_controller.ex:45: PhilomenaWeb.Image.TagChangeController.delete/2
(philomena 1.2.1) lib/philomena_web/controllers/image/tag_change_controller.ex:1: PhilomenaWeb.Image.TagChangeController.action/2
(philomena 1.2.1) lib/philomena_web/controllers/image/tag_change_controller.ex:1: PhilomenaWeb.Image.TagChangeController.phoenix_controller_pipeline/2
(phoenix 1.7.21) lib/phoenix/router.ex:484: Phoenix.Router.__call__/5
(philomena 1.2.1) lib/philomena_web/endpoint.ex:1: PhilomenaWeb.Endpoint.plug_builder_call/2
(philomena 1.2.1) deps/plug/lib/plug/debugger.ex:155: PhilomenaWeb.Endpoint."call (overridable 3)"/2
(philomena 1.2.1) lib/philomena_web/endpoint.ex:1: PhilomenaWeb.Endpoint.call/2
(phoenix 1.7.21) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(bandit 1.6.11) lib/bandit/pipeline.ex:130: Bandit.Pipeline.call_plug!/2
(bandit 1.6.11) lib/bandit/pipeline.ex:40: Bandit.Pipeline.run/4
(bandit 1.6.11) lib/bandit/http1/handler.ex:12: Bandit.HTTP1.Handler.handle_data/3
(bandit 1.6.11) lib/bandit/delegating_handler.ex:18: Bandit.DelegatingHandler.handle_data/3
(bandit 1.6.11) lib/bandit/delegating_handler.ex:8: Bandit.DelegatingHandler.handle_info/2
(stdlib 6.2.2) gen_server.erl:2345: :gen_server.try_handle_info/3
(stdlib 6.2.2) gen_server.erl:2433: :gen_server.handle_msg/6
(stdlib 6.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Code:
lib/philomena_web/controllers/image/tag_change_controller.ex
52 body: "Deleted tag change #{details} on image #{image.id} from history",
53 subject_path: ~p"/images/#{image}/tag_changes"
54 }
55 end
56
57> defp tag_change_details(%{user: %{name: name}, tags: tags}),
58 do: "by #{name} containing #{Enum.count(tags)} change(s)"
59 end
Called with 1 arguments
%Philomena.TagChanges.TagChange{__meta__: #Ecto.Schema.Metadata<:loaded, "tag_changes">, id: 12, user_id: nil, user: nil, image_id: 1, image: #Ecto.Association.NotLoaded<association :image is not loaded>, tags: [%Philomena.TagChanges.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tag_change_tags">, tag_change_id: 12, tag_change: #Ecto.Association.NotLoaded<association :tag_change is not loaded>, tag_id: 182, tag: %Philomena.Tags.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 182, aliased_tag_id: nil, aliased_tag: #Ecto.Association.NotLoaded<association :aliased_tag is not loaded>, aliases: #Ecto.Association.NotLoaded<association :aliases is not loaded>, channels: #Ecto.Association.NotLoaded<association :channels is not loaded>, implied_tags: #Ecto.Association.NotLoaded<association :implied_tags is not loaded>, implied_by_tags: #Ecto.Association.NotLoaded<association :implied_by_tags is not loaded>, verified_links: #Ecto.Association.NotLoaded<association :verified_links is not loaded>, public_links: #Ecto.Association.NotLoaded<association :public_links is not loaded>, hidden_links: #Ecto.Association.NotLoaded<association :hidden_links is not loaded>, dnp_entries: #Ecto.Association.NotLoaded<association :dnp_entries is not loaded>, slug: "meow", name: "meow", category: nil, images_count: 0, description: "", short_description: nil, namespace: nil, name_in_namespace: "meow", image: nil, image_format: nil, image_mime_type: nil, mod_notes: nil, uploaded_image: nil, removed_image: nil, implied_tag_list: nil, created_at: ~U[2025-06-16 18:57:37Z], updated_at: ~U[2025-06-16 18:57:37Z]}, added: true}], ip: %Postgrex.INET{address: {172, 18, 0, 7}, netmask: 32}, fingerprint: "d0547c7beb554b8", created_at: ~U[2025-06-16 19:12:04Z]}
Attempted function clauses (showing 1 out of 1)
defp tag_change_details(%{user: %{name: name}, tags: tags})
lib/philomena_web/controllers/image/tag_change_controller.ex
40
41 conn
42 |> put_flash(:info, "Successfully deleted tag change from history.")
43 |> moderation_log(
44 details: &log_details/2,
45> data: %{image: image, details: tag_change_details(tag_change)}
46 )
47 |> redirect(to: ~p"/images/#{image}/tag_changes")
48 end
49
50 defp log_details(_action, %{image: image, details: details}) do
lib/philomena_web/controllers/image/tag_change_controller.ex
1> defmodule PhilomenaWeb.Image.TagChangeController do
2 use PhilomenaWeb, :controller
3
4 alias Philomena.Images.Image
5 alias Philomena.TagChanges
6 alias Philomena.TagChanges.TagChange
lib/philomena_web/controllers/image/tag_change_controller.ex
1> defmodule PhilomenaWeb.Image.TagChangeController do
2 use PhilomenaWeb, :controller
3
4 alias Philomena.Images.Image
5 alias Philomena.TagChanges
6 alias Philomena.TagChanges.TagChange
lib/phoenix/router.ex
479 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
480 halted_conn
481
482 %Plug.Conn{} = piped_conn ->
483 try do
484> plug.call(piped_conn, plug.init(opts))
485 else
486 conn ->
487 measurements = %{duration: System.monotonic_time() - start}
488 metadata = %{metadata | conn: conn}
489 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
lib/philomena_web/endpoint.ex
1> defmodule PhilomenaWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :philomena
3
4 socket "/socket", PhilomenaWeb.UserSocket,
5 websocket: true,
6 longpoll: false
deps/plug/lib/plug/debugger.ex
150 case conn do
151 %Plug.Conn{path_info: ["__plug__", "debugger", "action"], method: "POST"} ->
152 Plug.Debugger.run_action(conn)
153
154 %Plug.Conn{} ->
155> super(conn, opts)
156 end
157 rescue
158 e in Plug.Conn.WrapperError ->
159 %{conn: conn, kind: kind, reason: reason, stack: stack} = e
160 Plug.Debugger.__catch__(conn, kind, reason, stack, @plug_debugger)
lib/philomena_web/endpoint.ex
1> defmodule PhilomenaWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :philomena
3
4 socket "/socket", PhilomenaWeb.UserSocket,
5 websocket: true,
6 longpoll: false
lib/phoenix/endpoint/sync_code_reload_plug.ex
17
18 def call(conn, {endpoint, opts}), do: do_call(conn, endpoint, opts, true)
19
20 defp do_call(conn, endpoint, opts, retry?) do
21 try do
22> endpoint.call(conn, opts)
23 rescue
24 exception in [UndefinedFunctionError] ->
25 case exception do
26 %UndefinedFunctionError{module: ^endpoint} when retry? ->
27 # Sync with the code reloader and retry once
lib/bandit/pipeline.ex
125 end
126 end
127
128 @spec call_plug!(Plug.Conn.t(), plug_def()) :: Plug.Conn.t() | no_return()
129 defp call_plug!(%Plug.Conn{} = conn, {plug, plug_opts}) when is_atom(plug) do
130> case plug.call(conn, plug_opts) do
131 %Plug.Conn{} = conn -> conn
132 other -> raise("Expected #{plug}.call/2 to return %Plug.Conn{} but got: #{inspect(other)}")
133 end
134 end
135
lib/bandit/pipeline.ex
35 conn = build_conn!(transport, method, request_target, headers, opts)
36 span = Bandit.Telemetry.start_span(:request, measurements, Map.put(metadata, :conn, conn))
37
38 try do
39 conn
40> |> call_plug!(plug)
41 |> maybe_upgrade!()
42 |> case do
43 {:no_upgrade, conn} ->
44 %Plug.Conn{adapter: {_mod, adapter}} = conn = commit_response!(conn)
45 Bandit.Telemetry.stop_span(span, adapter.metrics, %{conn: conn})
lib/bandit/http1/handler.ex
7 @impl ThousandIsland.Handler
8 def handle_data(data, socket, state) do
9 transport = %Bandit.HTTP1.Socket{socket: socket, buffer: data, opts: state.opts}
10 connection_span = ThousandIsland.Socket.telemetry_span(socket)
11
12> case Bandit.Pipeline.run(transport, state.plug, connection_span, state.opts) do
13 {:ok, transport} -> maybe_keepalive(transport, state)
14 {:error, _reason} -> {:close, state}
15 {:upgrade, _transport, :websocket, opts} -> do_websocket_upgrade(opts, state)
16 end
17 end
lib/bandit/delegating_handler.ex
13 |> handle_bandit_continuation(socket)
14 end
15
16 @impl ThousandIsland.Handler
17 def handle_data(data, socket, %{handler_module: handler_module} = state) do
18> handler_module.handle_data(data, socket, state)
19 |> handle_bandit_continuation(socket)
20 end
21
22 @impl ThousandIsland.Handler
23 def handle_shutdown(socket, %{handler_module: handler_module} = state) do
lib/bandit/delegating_handler.ex
3 # Delegates all implementation of the ThousandIsland.Handler behaviour
4 # to an implementation specified in state. Allows for clean separation
5 # between protocol implementations & friction free protocol selection &
6 # upgrades.
7
8> use ThousandIsland.Handler
9
10 @impl ThousandIsland.Handler
11 def handle_connection(socket, %{handler_module: handler_module} = state) do
12 handler_module.handle_connection(socket, state)
13 |> handle_bandit_continuation(socket)
gen_server.erl
gen_server.erl
proc_lib.erl
Connection details
Params
%{"_csrf_token" => "MwlwGDZKMjQAJwwDOxslI04-bAsEO3JaizIsOzxDxhZ7wTrH6XXfIUJh", "_method" => "delete", "id" => "12", "image_id" => "1"}
Request info
Headers
- accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
- accept-encoding: gzip, deflate
- accept-language: en-US,en;q=0.5
- content-length: 83
- content-type: application/x-www-form-urlencoded
- cookie: _philomena_key=XCP.QPwcSLlLSZmI5U_DGzB563rbkB_eSvek3Dl5OCGLg5MGYWWUT-3lkV18_aMForYW0yyr-kRuc36s-rMffdmzcg1sPzs6g00p4X8wo4txgY6MmYGCczBsLv6Q7jxCcuescIJyspWk13O3NfNUEVv0Psh4blNp9A_jn_aJuGnfPbwb0GLe_S3I-EyhZBCQB3IuBd0hb1K4uZ7G8wDoj3ZPFaCEOj0KrBlRQvLvmywcr7ma1Lb5TP9O5l4t0C7Fu6j-56FE-Xn8cjEpCdOAmmOt8zEqQz4F2k0E-fxbUcwJGAlrOsaW76xm9yRG8QGAEHYwQjDK0PwJ14M6PEsQ__PKoe1h_w37RIqmsV7qa6EgSCB1goYOinOAk5Axc; _ses=d0547c7beb554b8
- dnt: 1
- host: 172.19.129.35:8080
- origin: http://172.19.129.35:8080
- priority: u=0, i
- referer: http://172.19.129.35:8080/images/1/tag_changes
- sec-gpc: 1
- upgrade-insecure-requests: 1
- user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0
- via: 1.1 Caddy
- x-forwarded-for: 172.18.0.1
- x-forwarded-host: 172.19.129.35:8080
- x-forwarded-proto: http
Session
%{"_csrf_token" => "Zs9ky0JpxOV4LOWkxf4mMn82", "_ses" => "d0547c7beb554b8", "live_socket_id" => "users_sessions:L9qZTsionUR9HGFgs_JMz1Nj4_RkqQtyD46mG0sv7bM=", "remote_ip" => "172.18.0.7", "user_token" => <<47, 218, 153, 78, 200, 168, 157, 68, 125, 28, 97, 96, 179, 242, 76, 207, 83, 99, 227, 244, 100, 169, 11, 114, 15, 142, 166, 27, 75, 47, 237, 179>>}
Describe the bug
When attempting to remove a batch of tag changes made by an anonymous user from an images' tag change history, the request ends with a HTTP 500.
With debugging turned on, the issue at hand is a "FunctionClauseError". More in details.
Steps to reproduce the behavior:
Expected behavior
The tag changes batch should simply be removed from history.
Screenshots
https://i.imgur.com/q3vN5AR.png
Desktop (please complete the following information):
Additional context
FunctionClauseError at DELETE /images/1/tag_changes/12
Exception:
Code:
lib/philomena_web/controllers/image/tag_change_controller.exCalled with 1 arguments
%Philomena.TagChanges.TagChange{__meta__: #Ecto.Schema.Metadata<:loaded, "tag_changes">, id: 12, user_id: nil, user: nil, image_id: 1, image: #Ecto.Association.NotLoaded<association :image is not loaded>, tags: [%Philomena.TagChanges.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tag_change_tags">, tag_change_id: 12, tag_change: #Ecto.Association.NotLoaded<association :tag_change is not loaded>, tag_id: 182, tag: %Philomena.Tags.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 182, aliased_tag_id: nil, aliased_tag: #Ecto.Association.NotLoaded<association :aliased_tag is not loaded>, aliases: #Ecto.Association.NotLoaded<association :aliases is not loaded>, channels: #Ecto.Association.NotLoaded<association :channels is not loaded>, implied_tags: #Ecto.Association.NotLoaded<association :implied_tags is not loaded>, implied_by_tags: #Ecto.Association.NotLoaded<association :implied_by_tags is not loaded>, verified_links: #Ecto.Association.NotLoaded<association :verified_links is not loaded>, public_links: #Ecto.Association.NotLoaded<association :public_links is not loaded>, hidden_links: #Ecto.Association.NotLoaded<association :hidden_links is not loaded>, dnp_entries: #Ecto.Association.NotLoaded<association :dnp_entries is not loaded>, slug: "meow", name: "meow", category: nil, images_count: 0, description: "", short_description: nil, namespace: nil, name_in_namespace: "meow", image: nil, image_format: nil, image_mime_type: nil, mod_notes: nil, uploaded_image: nil, removed_image: nil, implied_tag_list: nil, created_at: ~U[2025-06-16 18:57:37Z], updated_at: ~U[2025-06-16 18:57:37Z]}, added: true}], ip: %Postgrex.INET{address: {172, 18, 0, 7}, netmask: 32}, fingerprint: "d0547c7beb554b8", created_at: ~U[2025-06-16 19:12:04Z]}Attempted function clauses (showing 1 out of 1)
lib/philomena_web/controllers/image/tag_change_controller.exlib/philomena_web/controllers/image/tag_change_controller.exlib/philomena_web/controllers/image/tag_change_controller.exlib/phoenix/router.exlib/philomena_web/endpoint.exdeps/plug/lib/plug/debugger.exlib/philomena_web/endpoint.exlib/phoenix/endpoint/sync_code_reload_plug.exlib/bandit/pipeline.exlib/bandit/pipeline.exlib/bandit/http1/handler.exlib/bandit/delegating_handler.exlib/bandit/delegating_handler.exgen_server.erlgen_server.erlproc_lib.erlConnection details
Params
Request info
Headers
Session