From a9f3b613cd085fe295fcad11d7c122194a76b2b3 Mon Sep 17 00:00:00 2001 From: Mahad Kalam <55807755+skyfallwastaken@users.noreply.github.com> Date: Fri, 20 Mar 2026 20:21:12 +0000 Subject: [PATCH] Fix crash when project name contains newline characters (#1096) Project names with newlines (e.g. from misbehaving editor plugins) caused ActionController::UrlGenerationError when loading /my/projects because Rails route constraints reject newlines in URL parameters. - Strip control characters from project names at heartbeat ingestion time - Detect newlines in broken_project_name? to mark such projects as broken - Skip URL generation for broken project names to prevent the crash https://claude.ai/code/session_016CtSDw18BePckUBQ2teeuQ Co-authored-by: Claude --- .../api/hackatime/v1/hackatime_controller.rb | 2 ++ .../my/project_repo_mappings_controller.rb | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/hackatime/v1/hackatime_controller.rb b/app/controllers/api/hackatime/v1/hackatime_controller.rb index 6070219..b2dba49 100644 --- a/app/controllers/api/hackatime/v1/hackatime_controller.rb +++ b/app/controllers/api/hackatime/v1/hackatime_controller.rb @@ -277,6 +277,8 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController source_type = :test_entry end + heartbeat[:project] = heartbeat[:project]&.gsub(/[[:cntrl:]]/, "")&.strip + attrs = heartbeat.merge({ user_id: @user.id, source_type: source_type, diff --git a/app/controllers/my/project_repo_mappings_controller.rb b/app/controllers/my/project_repo_mappings_controller.rb index 00fa596..ac2d09f 100644 --- a/app/controllers/my/project_repo_mappings_controller.rb +++ b/app/controllers/my/project_repo_mappings_controller.rb @@ -129,6 +129,8 @@ class My::ProjectRepoMappingsController < InertiaController mapping = mappings_by_name[project_key] display_name = project_key.presence || "Unknown" + broken = broken_project_name?(project_key, display_name) + url_safe = !broken && project_key.present? { id: project_card_id(project_key), @@ -139,12 +141,12 @@ class My::ProjectRepoMappingsController < InertiaController duration_percent: 0, repo_url: mapping&.repo_url, repository: repository_payload(mapping&.repository, latest_user_commit_at_by_repo_id), - broken_name: broken_project_name?(project_key, display_name), - manage_enabled: current_user.github_uid.present? && project_key.present?, - edit_path: project_key.present? ? edit_my_project_repo_mapping_path(project_key) : nil, - update_path: project_key.present? ? my_project_repo_mapping_path(project_key) : nil, - archive_path: project_key.present? ? archive_my_project_repo_mapping_path(project_key) : nil, - unarchive_path: project_key.present? ? unarchive_my_project_repo_mapping_path(project_key) : nil + broken_name: broken, + manage_enabled: current_user.github_uid.present? && url_safe, + edit_path: url_safe ? edit_my_project_repo_mapping_path(project_key) : nil, + update_path: url_safe ? my_project_repo_mapping_path(project_key) : nil, + archive_path: url_safe ? archive_my_project_repo_mapping_path(project_key) : nil, + unarchive_path: url_safe ? unarchive_my_project_repo_mapping_path(project_key) : nil } end.sort_by { |project| -project[:duration_seconds] } @@ -177,7 +179,7 @@ class My::ProjectRepoMappingsController < InertiaController key = project_key.to_s name = display_name.to_s - key.blank? || name.downcase == "unknown" || key.match?(/<<.*>>/) || name.match?(/<<.*>>/) + key.blank? || name.downcase == "unknown" || key.match?(/<<.*>>/) || name.match?(/<<.*>>/) || key.match?(/[\r\n]/) end def repository_payload(repository, latest_user_commit_at_by_repo_id = {})