diff --git a/release-notes/8.0.0/fix/3562.md b/release-notes/8.0.0/fix/3562.md
new file mode 100644
index 0000000000..8099056205
--- /dev/null
+++ b/release-notes/8.0.0/fix/3562.md
@@ -0,0 +1 @@
+Fixed a bug where subscribing to or unsubscribing from an issue in a repository with no code produced an internal server error.
diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go
index c8d7187b8e..5cff9f4ddd 100644
--- a/routers/web/repo/issue_watch.go
+++ b/routers/web/repo/issue_watch.go
@@ -46,7 +46,7 @@ func IssueWatch(ctx *context.Context) {
 		return
 	}
 
-	watch, err := strconv.ParseBool(ctx.Req.PostForm.Get("watch"))
+	watch, err := strconv.ParseBool(ctx.Req.PostFormValue("watch"))
 	if err != nil {
 		ctx.ServerError("watch is not bool", err)
 		return
diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
index f5b87231f3..f6daf0d146 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -692,6 +692,7 @@ type DeclarativeRepoOptions struct {
 	DisabledUnits optional.Option[[]unit_model.Type]
 	Files         optional.Option[[]*files_service.ChangeRepoFile]
 	WikiBranch    optional.Option[string]
+	AutoInit      optional.Option[bool]
 }
 
 func CreateDeclarativeRepoWithOptions(t *testing.T, owner *user_model.User, opts DeclarativeRepoOptions) (*repo_model.Repository, string, func()) {
@@ -706,11 +707,18 @@ func CreateDeclarativeRepoWithOptions(t *testing.T, owner *user_model.User, opts
 		repoName = gouuid.NewString()
 	}
 
+	var autoInit bool
+	if opts.AutoInit.Has() {
+		autoInit = opts.AutoInit.Value()
+	} else {
+		autoInit = true
+	}
+
 	// Create the repository
 	repo, err := repo_service.CreateRepository(db.DefaultContext, owner, owner, repo_service.CreateRepoOptions{
 		Name:          repoName,
 		Description:   "Temporary Repo",
-		AutoInit:      true,
+		AutoInit:      autoInit,
 		Gitignores:    "",
 		License:       "WTFPL",
 		Readme:        "Default",
@@ -742,6 +750,7 @@ func CreateDeclarativeRepoWithOptions(t *testing.T, owner *user_model.User, opts
 	// Add files, if any.
 	var sha string
 	if opts.Files.Has() {
+		assert.True(t, autoInit, "Files cannot be specified if AutoInit is disabled")
 		files := opts.Files.Value()
 
 		resp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, owner, &files_service.ChangeRepoFilesOptions{
diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go
index 49d1cbb016..e09656c4ee 100644
--- a/tests/integration/issue_test.go
+++ b/tests/integration/issue_test.go
@@ -22,6 +22,7 @@ import (
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/indexer/issues"
+	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/references"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
@@ -876,3 +877,23 @@ body:
 		})
 	})
 }
+
+func TestIssueUnsubscription(t *testing.T) {
+	onGiteaRun(t, func(t *testing.T, u *url.URL) {
+		defer tests.PrepareTestEnv(t)()
+
+		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+		repo, _, f := CreateDeclarativeRepoWithOptions(t, user, DeclarativeRepoOptions{
+			AutoInit: optional.Some(false),
+		})
+		defer f()
+		session := loginUser(t, user.Name)
+
+		issueURL := testNewIssue(t, session, user.Name, repo.Name, "Issue title", "Description")
+		req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/watch", issueURL), map[string]string{
+			"_csrf": GetCSRF(t, session, issueURL),
+			"watch": "0",
+		})
+		session.MakeRequest(t, req, http.StatusOK)
+	})
+}