You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Set an explicit gunicorn worker timeout in the website entrypoint
PM-groomed specification
Reviewed 2026-09-02 against origin/main at 2d3c28d1b8d1c2f3844f47f664945b42849d5eb9 (tree f7af08192c09361a3d064e8f8282b11d568ff211). This is an XS target-native
deployment hardening issue. It is independent of the unrelated dirty local
worktree edits and does not authorize provider or production changes.
Product outcome
The web branch of the website's target-owned entrypoint.sh gives Gunicorn
an explicit 90-second worker timeout. A slow configured outbound call therefore
reaches its own bounded client timeout first and can follow its ordinary safe
failure path instead of being killed at Gunicorn's implicit 30-second default.
The value is evidence-based: the largest currently configured application
outbound timeout is Datamailer's 60 seconds, and the selected 90 seconds adds a
10-second completion/logging margin. This matches the reviewed CMP precedent in e49c575ce3f567cc14a8f66a89d3ccaca3bfe2f4 without copying CMP files.
Current outbound-timeout inventory
Surface
Configured timeout
Runtime relevance
course_management/datamailer/client.py (DATAMAILER_TIMEOUT_SECONDS, default from website/settings/base.py)
60s
Largest application HTTP client; deployment currently leaves the Datamailer URL/API key disabled. An explicit future environment override above 80s is outside this issue and must be reviewed together with the deployment timeout contract.
Management-command transport; its SDK default is not a reviewed bound and is a separate hardening concern, not a reason to invent a Gunicorn value.
CI, E2E, deployment probes, local Git subprocesses, and test harness timeouts
are tooling/process budgets, not application outbound clients served by this
entrypoint. The website has no Relay client yet; the specifications require
future Relay network work to run only after commit through a durable worker.
Scope
Add the literal --timeout 90 pair to the Gunicorn command in the web) case
of target-owned entrypoint.sh.
Extend core.source_policy.validate_gunicorn_entrypoint so it parses that
web command and requires exactly one numeric --timeout value of 90; a
missing, duplicated, malformed, interpolated, or lower timeout fails closed.
Existing access-log privacy checks remain unchanged.
Add a focused regression to the existing source-policy test that reads the
real entrypoint.sh, checks the parser, and mutates/removes the timeout to
prove rejection. The regression must derive the safety assertion from course_management.datamailer.client.DEFAULT_TIMEOUT_SECONDS and assert gunicorn_timeout >= DEFAULT_TIMEOUT_SECONDS + 10, so a future change to the
reviewed Datamailer default cannot silently invalidate the relationship.
Explicit non-goals
Do not copy CMP's Dockerfile change or courses/tests/test_deploy_config.py;
the website's deployment contract is entrypoint.sh plus core.source_policy.
No Dockerfile/CMP source-pin/adoption-manifest change, container command
redesign, worker/job retry change, or new timeout environment variable.
No change to Datamailer enablement, client behavior, Relay integration,
email purpose/sender, S3/CloudWatch/URL-validator timeout policy, business
semantics, provider/network calls, production data, deployment, commit, or
push.
A deploy-time DATAMAILER_TIMEOUT_SECONDS override above 80 seconds is not
silently accepted by this issue; it requires a separately reviewed change to
the timeout contract/value.
Dependencies and ownership
This issue is target-owned and independently implementable. #305/#50 own the
Datamailer disablement/retirement and future Relay boundary; they are related
coordination, not prerequisites. No owner decision is required for this
fixed, source-evidenced 90-second hardening.
Acceptance criteria
entrypoint.sh's web) Gunicorn command contains exactly one literal --timeout 90; the worker) command is unchanged and no Dockerfile CMD is
used as the source of truth.
validate_gunicorn_entrypoint extracts/parses the web command and fails
closed for absent, duplicate, malformed, interpolated, or lower timeout
values while preserving the existing access-log privacy contract.
The focused regression proves the real command passes, each timeout
mutation fails, and the parsed 90 seconds is at least the current
Datamailer default plus the 10-second margin. It also proves the reviewed
30/3-second application client bounds do not exceed the selected value.
make migrations-check, the focused Django/source-policy tests, and the
graph-selected uv-backed quality/type/CI/container checks pass. No
migration is expected; a discovered model or deployment-schema change stops
the issue for re-grooming.
The engineer leaves an uncommitted frozen handoff with exact base/head,
plan digest, changed-file list, tests, and the timeout inventory. The
independent tester recomputes the plan and verifies all criteria before PM
acceptance.
Verification plan and browser evidence
This is an internal entrypoint/source-policy change with no product-page or
rendered error-surface change. The tester runs focused Django and applicable
control-plane/container checks plus the graph-selected backend Playwright tier;
screenshots are not_applicable only when the recomputed graph confirms no
render inputs changed. No browser/provider/network session is needed for the
acceptance claim.
After tester PASS, PM posts product acceptance. Only then may the engineer
create a focused commit with Closes #304; the orchestrator performs the
normal local no-ff merge/push and on-call observes resulting CI. No pull
request, deployment, or live send is part of this issue.
Set an explicit gunicorn worker timeout in the website entrypoint
PM-groomed specification
Reviewed 2026-09-02 against
origin/mainat2d3c28d1b8d1c2f3844f47f664945b42849d5eb9(treef7af08192c09361a3d064e8f8282b11d568ff211). This is an XS target-nativedeployment hardening issue. It is independent of the unrelated dirty local
worktree edits and does not authorize provider or production changes.
Product outcome
The
webbranch of the website's target-ownedentrypoint.shgives Gunicornan explicit 90-second worker timeout. A slow configured outbound call therefore
reaches its own bounded client timeout first and can follow its ordinary safe
failure path instead of being killed at Gunicorn's implicit 30-second default.
The value is evidence-based: the largest currently configured application
outbound timeout is Datamailer's 60 seconds, and the selected 90 seconds adds a
10-second completion/logging margin. This matches the reviewed CMP precedent in
e49c575ce3f567cc14a8f66a89d3ccaca3bfe2f4without copying CMP files.Current outbound-timeout inventory
course_management/datamailer/client.py(DATAMAILER_TIMEOUT_SECONDS, default fromwebsite/settings/base.py)content_sync/course_repository_sync.pyGitHub codeload fetchwebrequest path; included to show it is below 90s.courses/validators/custom_url_validators.pyinjected HEAD/GET transportcourse_management/observability/cloudwatch_dashboard.pyCI, E2E, deployment probes, local Git subprocesses, and test harness timeouts
are tooling/process budgets, not application outbound clients served by this
entrypoint. The website has no Relay client yet; the specifications require
future Relay network work to run only after commit through a durable worker.
Scope
--timeout 90pair to the Gunicorn command in theweb)caseof target-owned
entrypoint.sh.core.source_policy.validate_gunicorn_entrypointso it parses thatweb command and requires exactly one numeric
--timeoutvalue of 90; amissing, duplicated, malformed, interpolated, or lower timeout fails closed.
Existing access-log privacy checks remain unchanged.
real
entrypoint.sh, checks the parser, and mutates/removes the timeout toprove rejection. The regression must derive the safety assertion from
course_management.datamailer.client.DEFAULT_TIMEOUT_SECONDSand assertgunicorn_timeout >= DEFAULT_TIMEOUT_SECONDS + 10, so a future change to thereviewed Datamailer default cannot silently invalidate the relationship.
Explicit non-goals
Dockerfilechange orcourses/tests/test_deploy_config.py;the website's deployment contract is
entrypoint.shpluscore.source_policy.redesign, worker/job retry change, or new timeout environment variable.
email purpose/sender, S3/CloudWatch/URL-validator timeout policy, business
semantics, provider/network calls, production data, deployment, commit, or
push.
DATAMAILER_TIMEOUT_SECONDSoverride above 80 seconds is notsilently accepted by this issue; it requires a separately reviewed change to
the timeout contract/value.
Dependencies and ownership
This issue is target-owned and independently implementable. #305/#50 own the
Datamailer disablement/retirement and future Relay boundary; they are related
coordination, not prerequisites. No owner decision is required for this
fixed, source-evidenced 90-second hardening.
Acceptance criteria
entrypoint.sh'sweb)Gunicorn command contains exactly one literal--timeout 90; theworker)command is unchanged and no Dockerfile CMD isused as the source of truth.
validate_gunicorn_entrypointextracts/parses the web command and failsclosed for absent, duplicate, malformed, interpolated, or lower timeout
values while preserving the existing access-log privacy contract.
mutation fails, and the parsed 90 seconds is at least the current
Datamailer default plus the 10-second margin. It also proves the reviewed
30/3-second application client bounds do not exceed the selected value.
make migrations-check, the focused Django/source-policy tests, and thegraph-selected
uv-backed quality/type/CI/container checks pass. Nomigration is expected; a discovered model or deployment-schema change stops
the issue for re-grooming.
plan digest, changed-file list, tests, and the timeout inventory. The
independent tester recomputes the plan and verifies all criteria before PM
acceptance.
Verification plan and browser evidence
This is an internal entrypoint/source-policy change with no product-page or
rendered error-surface change. The tester runs focused Django and applicable
control-plane/container checks plus the graph-selected backend Playwright tier;
screenshots are
not_applicableonly when the recomputed graph confirms norender inputs changed. No browser/provider/network session is needed for the
acceptance claim.
After tester PASS, PM posts product acceptance. Only then may the engineer
create a focused commit with
Closes #304; the orchestrator performs thenormal local no-ff merge/push and on-call observes resulting CI. No pull
request, deployment, or live send is part of this issue.