Say when something other than Ployz answers on port 80#1682

merged5
main
t3/port80-probe-reason
openedOct 12, 2026byNNick
View on GitHubGitHub import enabledPR last imported 21d ago

A Server where another web server holds port 80 showed Port 80 is closed on …, even though port 80 answered. A user who self-hosts Ployz Cloud behind Caddy on a Ployz Server hit this and could not tell what was wrong.

The ingress probe now reports which of three answers it got:

  • ployz: Ployz's Caddy answered with this Server's Machine id.
  • taken: some other HTTP response came back (another status, a redirect, or another body).
  • closed: no answer (refused, timed out, or a network error).

A taken Server shows Something else answers on port 80 of …. In the dashboard the message adds "Free the port for Ployz.", and it is a new port_80_taken status in core and the CLI. Stored traffic gains an optional taken list. Rows saved before this change still read as closed, so no migration is needed.

When one Server is closed and another is taken, the closed message shows first. The other appears after Check again.

The user docs add a status row and a troubleshooting section for the new message.

The first commit is a probe test against real local HTTP servers. It failed before the fix.

Checks

16 successful Β· 1 skipped
All checks passed
46d3f64

Added 2 commits

  • Test that the ingress probe tells a closed port 80 from one something else answers006ea76
  • Say when something other than Ployz answers on port 804bf99c9
Synced from GitHub

Review in Change Stack β†’

No actionable comments were generated in the recent review. πŸŽ‰

ℹ️ Recent review info
βš™οΈ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4658b745-19a0-463f-8bf2-8c2dac693835

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between cb25ff663e6135c13a57e8fe44fe9cc17ef60814 and 46d3f64adaf5f296e5eb809016f3cc98e7a20bc7.


β›” Files ignored due to path filters (2)
  • core/crates/ployz-sdk/generated/config-wasm.mjs is excluded by !**/generated/**
  • core/crates/ployz-sdk/generated/payloads.d.ts is excluded by !**/generated/**

πŸ“’ Files selected for processing (16)
  • core/crates/ployz-store/src/domain/mod.rs
  • core/crates/ployz-store/src/domain/status.rs
  • dashboard/src/modules/cluster-domain/cluster-domain.test.ts
  • dashboard/src/modules/cluster-domain/cluster-domain.ts
  • dashboard/src/modules/cluster-domain/ingress-probe.server.test.ts
  • dashboard/src/modules/cluster-domain/ingress-probe.server.ts
  • dashboard/src/modules/cluster-domain/sync.inngest.postgres.test.ts
  • dashboard/src/modules/cluster-domain/sync.server.ts
  • dashboard/src/modules/cluster-domain/tables.ts
  • dashboard/src/modules/config-store/domain-evidence.server.ts
  • dashboard/src/modules/runtime/effect-try-promise-boundary.test.ts
  • dashboard/src/routes/_protected/cloud/$organizationSlug/_org/-components/ClusterDomainSection.test.tsx
  • dashboard/src/routes/_protected/cloud/$organizationSlug/_org/-components/ClusterDomainSection.tsx
  • docs/user/self-hosting.md
  • docs/user/services/domains.md
  • docs/user/troubleshooting/app-not-reachable.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.



πŸ“ Summary

Summary by CodeRabbit

  • New Features
    • Domain checks now distinguish servers where another service is answering on port 80 from servers where port 80 is unreachable.
    • The domain status identifies affected servers and provides a check-again option.
  • Documentation
    • Updated domain and troubleshooting guidance explains how to free port 80 and check the domain again.

Walkthrough

Ingress checks now distinguish servers where another service answers on port 80 from unreachable servers. The recorded addresses feed domain status, dashboard messaging, and updated troubleshooting guidance.

Changes

Port 80 Conflict Detection

Layer / File(s)Summary
Classify ingress probe responses
dashboard/src/modules/cluster-domain/ingress-probe.server.ts, dashboard/src/modules/cluster-domain/ingress-probe.server.test.ts, dashboard/src/modules/runtime/effect-try-promise-boundary.test.ts
The probe classifies matching verification responses as ployz, other HTTP responses as taken, and fetch failures as closed. Tests cover these outcomes.
Store taken ingress addresses
dashboard/src/modules/cluster-domain/sync.server.ts, dashboard/src/modules/cluster-domain/tables.ts, dashboard/src/modules/cluster-domain/sync.inngest.postgres.test.ts
Synchronization records reachable, unreachable, and taken servers. Probed traffic stores the taken list, which is optional for older rows.
Expose port 80 conflicts in domain status
core/crates/ployz-store/src/domain/mod.rs, core/crates/ployz-store/src/domain/status.rs, dashboard/src/modules/cluster-domain/cluster-domain.ts, dashboard/src/modules/cluster-domain/cluster-domain.test.ts, dashboard/src/modules/config-store/domain-evidence.server.ts
Store and dashboard status logic expose port_80_taken with affected addresses. Tests cover taken servers and mixed unreachable/taken results.
Display port conflict guidance
dashboard/src/routes/_protected/cloud/$organizationSlug/_org/-components/ClusterDomainSection.tsx, dashboard/src/routes/_protected/cloud/$organizationSlug/_org/-components/ClusterDomainSection.test.tsx, docs/user/self-hosting.md, docs/user/services/domains.md, docs/user/troubleshooting/app-not-reachable.md
The dashboard identifies affected addresses and offers β€œCheck again.” The documentation describes the port conflict and troubleshooting steps.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant ClusterDomainSync
3 participant probeIngress
4 participant IngressServer
5 participant ClusterDomainTraffic
6 ClusterDomainSync->>probeIngress: Probe origin with machine ID
7 probeIngress->>IngressServer: Request /.ployz-verify
8 IngressServer-->>probeIngress: Return HTTP response or connection failure
9 probeIngress-->>ClusterDomainSync: Classify as ployz, taken, or closed
10 ClusterDomainSync->>ClusterDomainTraffic: Store server addresses by classification

Merge Risk | βšͺ Minimal Β· up to 46d3f

Merge Risk: βšͺ Minimal Β· up to 46d3f

The port-80 conflict behavior is consistent with the stated change. No issue identified here needs to be resolved before merging.

Security Architecture Review

Security architecture risk: πŸ”΅ Low Β· up to 46d3f

The change improves port-conflict diagnostics without expanding probe destinations or allowing unverified responders into newly published DNS records. Older saved rows remain readable. Deployment-version coordination is not established, so diagnostic consistency during rollback remains uncertain.

Retained concerns No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred β€” A responder at a selected ingress address can influence that organization's observed port status. The PR does not broaden destination selection: requests still target ingress machines from the connected organization's runtime frame. The new taken outcome adds no DNS, certificate, or configuration authority.

Security Findings and Attack Paths

  • inferred β€” No introduced security attack path was identified in the reviewed classification flow. An unexpected or interrupted response becomes taken, but remains excluded from reachable results. Existing HTTP probe exposure and retention of the last published DNS set are unchanged, rather than newly introduced guarantees or vulnerabilities.

Trust Boundaries and Controls

  • observed β€” The outbound probe retains manual redirects and a five-second timeout combined with effect cancellation. Only status 200 with a trimmed body equal to the expected machine ID yields Ployz. This is the existing identity-correlation check over HTTP, not newly added cryptographic responder authentication.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring CoverageWarningDocstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 13 files. (3 skipped: …Write docstrings for the functions missing them to satisfy the coverage threshold.
βœ… Passed checks (4 passed)
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the main change: reporting when another service answers on port 80.
Description checkPassedThe description accurately explains the three probe outcomes, new status handling, stored traffic changes, dashboard behavior, tests, and documentation updates.
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.

Full details: Docstring Coverage

Explanation

Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 13 files. (3 skipped: 3 unsupported.)


  • Fix all pre-merge checks with AI
✨ Finishing Touches πŸ’‘ 1
πŸ“ Generate docstrings πŸ’‘
  • Commit to this branch
  • Create a new PR

πŸ§ͺ Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix Β· Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Added 2 commits

  • Type the empty unreachable list in the taken-port test7aec94c
  • Allow the ingress probe its HTTP boundarydb644a6
Synced from GitHub

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

ReviewStatusCommitReview trigger
πŸ“ Code Reviewβœ… Completed 2026-10-12T18:53:16.252470Z46d3f64New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with πŸ‘€ while any review is running, comments if it has suggestions, and reacts with πŸ‘ once all reviews finish with no findings.

Cchatgpt-codex-connectorBotreviewed Β· 1 inline threadOct 12, 2026
Synced from GitHub

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: db644a6f5c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

……@@ -0,0 +1,18 @@
14+ redirect: "manual",
15+ signal: AbortSignal.any([signal, AbortSignal.timeout(PROBE_TIMEOUT_MS)]),
16+ });
17+ return response.status === 200 && (await response.text()).trim() === machineId ? "ployz" : "taken";

P2 Badge Preserve an observed HTTP response when its body read fails

When a server returns 200 headers but stalls, truncates, or sends an invalidly encoded body, fetch has already proved that port 80 answered, yet response.text() throws and the outer orElseSucceed classifies it as closed. The UI and CLI will therefore still report β€œPort 80 is closed” for some non-Ployz HTTP responders, defeating the distinction added here; handle body-read failures after receiving a response as taken (and preferably bound the body read), reserving closed for failures before any response.

AGENTS.md reference: AGENTS.md:L6-L6

Useful? React with πŸ‘Β / πŸ‘Ž.

Fixed in 46d3f64ad. A 200 whose body read fails now counts as taken; only a failure before any response is closed. The body read shares the fetch's 5 s timeout signal, so it is already bounded. Added a test that truncates the body mid-stream.

Added 1 commit

  • Count a response whose body fails as something else answering46d3f64