diff --git a/cmd/joj3/main.go b/cmd/joj3/main.go
index 9842e25..24733e2 100644
--- a/cmd/joj3/main.go
+++ b/cmd/joj3/main.go
@@ -34,7 +34,6 @@ func mainImpl() (err error) {
 	confObj := new(conf.Conf)
 	var stageResults []internalStage.StageResult
 	var forceQuitStageName string
-	var teapotCheckResults []teapot.CheckResult
 	var teapotRunResult teapot.RunResult
 	var commitMsg string
 	defer func() {
@@ -109,16 +108,8 @@ func mainImpl() (err error) {
 		return err
 	}
 	groups := conf.MatchGroups(confObj, conventionalCommit)
-	if len(confObj.Teapot.Groups) != 0 {
-		teapotCheckResults, err = teapot.Check(confObj)
-		if err != nil {
-			slog.Error("teapot check", "error", err)
-		}
-	} else {
-		slog.Info("teapot check disabled")
-	}
 	stageResults, forceQuitStageName, err = stage.Run(
-		confObj, groups, teapotCheckResults,
+		confObj, groups,
 	)
 	if err != nil {
 		slog.Error("stage run", "error", err)
diff --git a/cmd/joj3/stage/run.go b/cmd/joj3/stage/run.go
index afcbc2e..1e62441 100644
--- a/cmd/joj3/stage/run.go
+++ b/cmd/joj3/stage/run.go
@@ -5,7 +5,6 @@ import (
 	"log/slog"
 	"strings"
 
-	"github.com/joint-online-judge/JOJ3/cmd/joj3/teapot"
 	"github.com/joint-online-judge/JOJ3/internal/conf"
 	executors "github.com/joint-online-judge/JOJ3/internal/executor"
 	_ "github.com/joint-online-judge/JOJ3/internal/parser"
@@ -131,67 +130,9 @@ func newErrorStageResults(err error) ([]stage.StageResult, string) {
 	}, "Internal Error"
 }
 
-func newTeapotCheckStageResults(
-	checkResults []teapot.CheckResult,
-	groups []string,
-) (stageResults []stage.StageResult, forceQuitStageName string, err error) {
-	if len(checkResults) == 0 {
-		return
-	}
-	comment := ""
-	forceQuit := false
-	for _, checkResult := range checkResults {
-		useGroup := false
-		if checkResult.Name != "" {
-			comment += fmt.Sprintf("keyword `%s` ", checkResult.Name)
-		} else {
-			useGroup = true
-		}
-		for _, group := range groups {
-			if strings.EqualFold(group, checkResult.Name) {
-				useGroup = true
-				break
-			}
-		}
-		comment += fmt.Sprintf(
-			"in last %d hour(s): submit count %d, max count %d\n",
-			checkResult.TimePeriod,
-			checkResult.SubmitCount,
-			checkResult.MaxCount,
-		)
-		if useGroup && checkResult.SubmitCount+1 > checkResult.MaxCount {
-			forceQuit = true
-			err = fmt.Errorf("submit count exceeded")
-		}
-	}
-	stageResults = []stage.StageResult{
-		{
-			Name: "Teapot Check",
-			Results: []stage.ParserResult{{
-				Score:   0,
-				Comment: comment,
-			}},
-			ForceQuit: forceQuit,
-		},
-	}
-	forceQuitStageName = "Teapot Check"
-	return
-}
-
-func Run(
-	conf *conf.Conf, groups []string, checkResults []teapot.CheckResult,
-) (
+func Run(conf *conf.Conf, groups []string) (
 	stageResults []stage.StageResult, forceQuitStageName string, err error,
 ) {
-	stageResults, forceQuitStageName, err = newTeapotCheckStageResults(
-		checkResults,
-		groups,
-	)
-	if err != nil {
-		slog.Error("teapot check", "error", err)
-		conf.Teapot.SkipScoreboard = true // avoid adding extra submit count
-		return
-	}
 	executors.InitWithConf(
 		conf.Stage.SandboxExecServer,
 		conf.Stage.SandboxToken,
@@ -203,12 +144,11 @@ func Run(
 		return
 	}
 	defer stage.Cleanup()
-	newStageResults, forceQuitStageName, err := stage.Run(stages)
+	stageResults, forceQuitStageName, err = stage.Run(stages)
 	if err != nil {
 		slog.Error("run stages", "error", err)
 		stageResults, forceQuitStageName = newErrorStageResults(err)
 		return
 	}
-	stageResults = append(stageResults, newStageResults...)
 	return
 }
diff --git a/cmd/joj3/teapot/check.go b/cmd/joj3/teapot/check.go
deleted file mode 100644
index 7cf458d..0000000
--- a/cmd/joj3/teapot/check.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package teapot
-
-import (
-	"encoding/json"
-	"fmt"
-	"log/slog"
-	"os"
-	"strings"
-
-	"github.com/joint-online-judge/JOJ3/cmd/joj3/env"
-	"github.com/joint-online-judge/JOJ3/internal/conf"
-)
-
-type CheckResult struct {
-	Name        string `json:"name"`
-	SubmitCount int    `json:"submit_count"`
-	MaxCount    int    `json:"max_count"`
-	TimePeriod  int    `json:"time_period"`
-}
-
-func Check(conf *conf.Conf) (checkResults []CheckResult, err error) {
-	os.Setenv("LOG_FILE_PATH", conf.Teapot.LogPath)
-	os.Setenv("_TYPER_STANDARD_TRACEBACK", "1")
-	if env.Attr.Actor == "" ||
-		env.Attr.Repository == "" ||
-		strings.Count(env.Attr.Repository, "/") != 1 {
-		slog.Error("teapot env not set")
-		err = fmt.Errorf("teapot env not set")
-		return
-	}
-	repoParts := strings.Split(env.Attr.Repository, "/")
-	repoName := repoParts[1]
-	var formattedGroups []string
-	for _, group := range conf.Teapot.Groups {
-		groupConfig := fmt.Sprintf("%s=%d:%d",
-			group.Name, group.MaxCount, group.TimePeriodHour)
-		formattedGroups = append(formattedGroups, groupConfig)
-	}
-	args := []string{
-		"joj3-check", conf.Teapot.EnvFilePath,
-		env.Attr.Actor, conf.Teapot.GradingRepoName, repoName,
-		conf.Teapot.ScoreboardPath, conf.Name,
-		"--group-config", strings.Join(formattedGroups, ","),
-	}
-	stdoutBuf, err := runCommand(args)
-	if err != nil {
-		slog.Error("teapot check exec", "error", err)
-		return
-	}
-	if json.Unmarshal(stdoutBuf.Bytes(), &checkResults) != nil {
-		slog.Error("unmarshal teapot result", "error", err,
-			"stdout", stdoutBuf.String())
-		return
-	}
-	slog.Info("teapot result", "result", checkResults)
-	return
-}