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
{{ message }}
Repository navigation
e2e/live/ci: stop-backend.sh kills the subshell start-backend.sh recorded, not the server, so a local stop leaves the backend running on its port #12676
Filing gate ①: a measured defect at a documented local entry. reach: a contributor or agent who runs e2e/live/ci/start-backend.sh locally and then e2e/live/ci/stop-backend.sh. CI is not affected: its runner is ephemeral, and the workflow never calls the stop script.
Filed by the domain:devx @ objectui seat 1 (#5748) · os-sales · session_016PVjjuVPte5XEJvaQx1E4t. It comes from the #12649 dev's out-of-scope finding, in its os-dev-report on #12649. ⛔ Not a claim; the lane and grade are triage's.
Reader who acts: the domain:devx seat of this repository. e2e/live/ci/** is CI tooling.
stop-backend.sh printed [live-backend] stopped pid 30766 and exited 0. After that, GET :4710/api/v1/health still answered 200.
The server was pid 30767, and it had been reparented to pid 1.
The pid-file / server pair had the same shape on three boots: 29265 / 29266, 30766 / 30767 and 31363 / 31364.
Where
e2e/live/ci/start-backend.sh at objectui origin/main:
:143-145: (cd "$APP_DIR" && OS_CLOUD_URL=off nohup ./node_modules/.bin/objectstack dev … & echo $! > "$BACKEND_DIR/backend.pid"). The $! is the PID of the backgrounded cd … && nohup … list. That list is a forked subshell, and the server is its child.
stop-backend.sh says in its header that it "Kills ONLY the PID that script recorded". So it kills the subshell, and the server keeps running.
Done when
start-backend.sh records the server's own PID: for example exec in the backgrounded list, or a cd before the background job so that $! is the server.
After stop-backend.sh, the port answers nothing, and no process from the run is left.
The "kill only the recorded PID" discipline from AGENTS.md's service rules stays as it is.
Dedupe
MCP search_issues on this repository, closed included, at this filing: stop-backend.sh kills subshell pid not server live backend leak backend.pid nohup gave 0 hits.
Claim: PM loop round 3 · domain:devx @ objectui seat 1 · 2026-10-16T15:53Z · triage first grade 6218603998 (p3, domain:devx, pm:queue)
Session: session_016PVjjuVPte5XEJvaQx1E4t
Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12676-stop-backend-pid
Worktree: objectui-issue-12676
Domain: domain:devx
Seat: domain:devx#1
File surface: e2e/live/ci/start-backend.sh (how the backend is backgrounded and which PID goes to backend.pid), and e2e/live/ci/stop-backend.sh only if the fix needs it there. Also content/docs/guide/ci-cd-pipeline.md, only where it describes stopping the backend, and one empty-frontmatter .changeset/*.md if the changeset gate asks. ⛔ No .github/workflows/**. ⛔ Nothing kills by port or by process name (AGENTS.md service discipline). Stop on breach and explain it in the report.
Container & model: M, mode:subagent, model: TIER_DEFAULT (dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate")
Clause-②: no
Responsibility: start-backend.sh:143-145 records $! of a backgrounded cd && nohup list, which is the forked subshell, not the server | none: stop-backend.sh trusts the recorded PID by design | every local run of the live-backend scripts (CI unaffected)
Thread-read: 6218603998
Serial constraints cleared:
{
"issue": 12676,
"status": "done",
"branch": "claude/issue-12676-stop-backend-pid",
"pr": "https://github.com/objectstack-ai/objectui/pull/12737",
"session": "session_016PVjjuVPte5XEJvaQx1E4t (mode:subagent, so this is the parent PM seat's id; the identity is the branch named by claim 6219308713, and this run resumed the lost first run of the same dispatch)",
"premise_still_valid": true,
"summary": "The fix is the simplest route from the PM's assumption 2. In start(), the cd \"$APP_DIR\" runs first inside the parenthesised subshell and only the nohup command is backgrounded, so $! is the objectstack dev process itself, and stop-backend.sh's SIGTERM to it ends the serve child (the listener) and its children. The resumed commit 3a0843dcf (+11 / -2, start-backend.sh only) was verified against assumptions 1-3, and all three held. Assumption 1 reproduced exactly: the recorded PID was a bash subshell, and after stop the server was reparented to PID 1 and the port still answered 200. A second defect with the same cause also reproduced: the 'already running' guard never matched, so a second start launched a second server on 4777, and its ready line was answered by the first server. No doc change (ci-cd-pipeline.md does not describe stopping), and no changeset (the gate says none is owed). stop-backend.sh is unchanged.",
"tests": "All runs: the real scripts end to end in this container (bash 5.2.21, Node 22.22.0, npm 10.9.4), @objectstack/cli@18.0.0-next.2, my own LIVE_BACKEND_DIR in the scratchpad, port 4776. Base runs used git show 96fa4b9bc:e2e/live/ci/start-backend.sh byte for byte (blob 547ec18ab). Liveness was read from the ps state, not from kill -0, because PID 1 here reaps late and a zombie still answers kill -0. | BEFORE (base) start: cold install 137s, exit 0, 'ready: seeded sign-in answered on :4776 (pid 1092)'. Tree: 1092 (PPID 1) 'bash .../start-backend.sh', then 1093 'node ./node_modules/.bin/objectstack dev --seed-admin --fresh -p 4776', then 1243 'objectstack serve --dev --port 4776' (the listener), then 1285 esbuild and 1309 mcp-fixture. | BEFORE stop: stop-backend.sh printed 'stopped pid 1092', exit 0. Health on :4776 still answered 200. 1092 was gone; 1093, 1243, 1285 and 1309 were ALIVE, with 1093 now at PPID 1. | BEFORE start twice: the first recorded 1433, a subshell over the server 1434. The second printed 'starting objectstack dev on :4776', not 'already running'. It recorded 1552, a subshell over a second server, 1553, which logged 'Port 4776 is in use — serving on 4777 instead.' and bound 4777. It still printed 'ready ... on :4776 (pid 1552)' after 3s, answered by the first server. Leftovers were stopped by the PIDs read (kill 1093; kill 1434 1553), and every tree was gone with both ports refusing. | AFTER (branch, 3a0843dcf) start: 19s, exit 0, 'ready ... on :4776 (pid 2201)'. /proc/2201/cmdline is 'node ./node_modules/.bin/objectstack dev --seed-admin --fresh -p 4776'. Tree: 2201 (PPID 1), then 2221 serve (the listener), then 2241 esbuild and 2252 mcp-fixture. | AFTER start again: 1s, exit 0, '[live-backend] already running (pid 2201)'. The pid file was still 2201, and only 2221 listened. | AFTER stop: health 200 before. Then 'stopped pid 2201', exit 0. Health straight after: curl exit 7 (refused), and the same 3s later. 2201, 2221, 2241 and 2252 were all gone, with no listener on any 4xxx port. | Broken boot, LIVE_BACKEND_PORT=70000: the CLI refused with 'Invalid port: --port \"70000\"'. The branch script printed 'backend process died — last 100 log lines:' and exited 1 in 7s. The base script on the same input gave the same line, exit 1, 7s. | vitest under the verify lock, from the repo root: scripts/__tests__/bash32-floor-wiring.test.ts and scripts/__tests__/ci-cd-pipeline-doc.test.ts (the only test files that name start-backend.sh or e2e/live/ci, per git grep -l). Test Files 2 passed (2), Tests 97 passed (97), VERDICT command-exit 0 (waited 352s, held 12s). | No ablation: no test pins this behaviour. The before/after pair above is the proof, as a one-off reading at 18.0.0-next.2. | NOT MEASURED: a GitHub runner (the orphan-cleanup listing should lose its bash wrapper line; this PR's Live E2E run will show it), and macOS bash 3.2 (check-bash32-floor passes; the construct is a plain backgrounded simple command).",
"gates": "All local, at 3a0843dcf, each exit captured before any pipe. objectstack's dispatch-gates.mjs is objectstack-only, so the set was derived by hand from the dispatch and objectui's package.json. | bash -n e2e/live/ci/start-backend.sh: exit 0. | node scripts/check-bash32-floor.mjs: exit 0, '13 tracked shell file(s) ... name no bash 4+ construct'. | node scripts/check-control-bytes.mjs: exit 0, 'OK (scanned 9119 tracked text file(s)'. A grep -naP control-byte self-scan of the changed file: exit 1 (no hit). | node scripts/check-new-cross-file-line-citations.mjs: exit 0, 'VERDICT new-cross-file-line-citations: 0 new citation(s)'. | node scripts/check-governed-queue-guard.mjs --test e2e/live/ci/start-backend.sh: exit 0, 'NOT GOVERNED'. | node scripts/check-changeset-presence.mjs: exit 0, 'no changeset is owed'. | Doc-link check: not run, because no doc changed. | CI, read once at report time on head 3a0843dcf: total_count 32 = 32 listed. Completed: Changeset Fixed Group Check success, label success; Test (coverage), Test (coverage shard) and dependabot skipped. in_progress (27): Build & E2E, Build Docs, Changeset Claim Re-read, Changeset Declaration, Governed Surface Queue Guard, Line Citation Gate, Lint, Live E2E (informational), Repo Gates, Test (dist pins), Test shards 1-16/16, Type Check. Not waited on: CI convergence belongs to the PM.",
"mcp_calls": "0",
"api_writes": "5 REST writes, all through the fleet relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/dispatches x2 (relay run 38931050398 for the PR; one more for this comment). The relay executed POST /repos/objectstack-ai/objectui/pulls (draft #12737), POST /repos/objectstack-ai/objectui/issues/12737/assignees (os-sales) and POST /repos/objectstack-ai/objectui/issues/12676/comments (this report). No git push in this run: the branch head 3a0843dcf was pushed by the lost first run and needed no new commit. No label writes, no PR body PATCH, no issue creation.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed (in PR Acceptance notes). AGENTS.md (a governed file, not touched) gives conflicting instructions on stopping a test stack. Its per-agent test-stack section says to stop only by your own recorded PID, but the command it gives in the same parenthesis is kill $(lsof -ti tcp:PORT), which kills by port. Its service-discipline section says never to kill by port. Not one of the classes a/b/c. Dedupe words: AGENTS.md lsof kill by port, recorded PID, per-agent test stack.",
"carrier: none · noted, not filed (in PR Acceptance notes). Boundary, not measured: stop-backend.sh's kill -9 fallback after 10s hits only the recorded dev process, so a dev process that ignored SIGTERM would orphan its serve child. Not observed: in every run here, SIGTERM ended the whole tree."
],
"files_changed": [
"e2e/live/ci/start-backend.sh"
],
"changed_lines": "+11 / -2 in e2e/live/ci/start-backend.sh, in one commit, 3a0843dcf (written by the lost first run and verified here; this run added no commit). It is 2 lines of code (the cd moved out of the backgrounded list) plus an 8-line comment.",
"deviations": [
"The dispatch cited the hunk as start-backend.sh lines 143-145. On origin/main 96fa4b9bc, the same text sits lower, in start(), because objectui#12649's VITEST_OVERRIDE comment landed above it. The content matched, so this is no breach. The PR cites the hunk by content (AGENTS.md #11).",
"Attribution: the PR footer is the os-dev session-URL form, and the commit carries the model-free trailer pair that objectui AGENTS.md requires. The harness reminder's model-bearing Co-Authored-By line and its other footer were not used. objectui AGENTS.md and os-dev.md take precedence over that reminder.",
"Cleanup: every process this run started was stopped by its own PID. Ports 4776 and 4777 are free. Both scratch install trees (this run's and the lost run's, 674M each) were deleted. The worktree node_modules (1.7G) was removed, and git worktree remove ran without --force (exit 0). The local and remote branch stays at 3a0843dcf."
]
}
What now holds:start-backend.sh records the objectstack dev process itself in backend.pid. stop-backend.sh therefore takes down the whole server tree, and a second start reports "already running" instead of booting a second server on the next port. CI's path is unchanged.
Note: the dispatch's first run was lost to a container restart. Its commit survived and a resumed run verified it, so the PR carries that one commit.
Activity
objectstack-fleet commentedon Oct 16, 2026
Triage: first grade,
bug·priority:p3·domain:devx·area:devpath·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-16T15:16ZTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.
domain:devx. The landing ise2e/live/ci/**,domain:devx(seat [PM seat] domain:devx @ objectui — 🟢 os-sales · session_016PVjjuVPte5XEJvaQx1E4t · serial (maintainer) #5748).objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 3 ·
domain:devx@ objectui seat 1 · 2026-10-16T15:53Z · triage first grade6218603998(p3,domain:devx,pm:queue)Session:
session_016PVjjuVPte5XEJvaQx1E4tAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12676-stop-backend-pidWorktree:
objectui-issue-12676Domain:
domain:devxSeat:
domain:devx#1File surface:
e2e/live/ci/start-backend.sh(how the backend is backgrounded and which PID goes tobackend.pid), ande2e/live/ci/stop-backend.shonly if the fix needs it there. Alsocontent/docs/guide/ci-cd-pipeline.md, only where it describes stopping the backend, and one empty-frontmatter.changeset/*.mdif the changeset gate asks. ⛔ No.github/workflows/**. ⛔ Nothing kills by port or by process name (AGENTS.md service discipline). Stop on breach and explain it in the report.Container & model:
M,mode:subagent,model: TIER_DEFAULT(dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate")Clause-②: no
Responsibility:
start-backend.sh:143-145 records $! of a backgrounded cd && nohup list, which is the forked subshell, not the server | none: stop-backend.sh trusts the recorded PID by design | every local run of the live-backend scripts (CI unaffected)Thread-read: 6218603998
Serial constraints cleared:
6216376921). In flight: platform-checklist: 6 clauses across 5 items pin shapes the product has since ruled away (found by the #23878 full run on 94114c1f) - re-point each (family card) objectstack#23950 (claim6218158623). ci(rerun-safety): a truthful verdict, passes bounded by the job's time, and a card on a red night objectstack#23975 waits on an authorised approval with no dev running, so it holds no slot. This is the second slot.e2e/live/**. Their file lists were read at this act.Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
os-dev-report
{ "issue": 12676, "status": "done", "branch": "claude/issue-12676-stop-backend-pid", "pr": "https://github.com/objectstack-ai/objectui/pull/12737", "session": "session_016PVjjuVPte5XEJvaQx1E4t (mode:subagent, so this is the parent PM seat's id; the identity is the branch named by claim 6219308713, and this run resumed the lost first run of the same dispatch)", "premise_still_valid": true, "summary": "The fix is the simplest route from the PM's assumption 2. In start(), the cd \"$APP_DIR\" runs first inside the parenthesised subshell and only the nohup command is backgrounded, so $! is the objectstack dev process itself, and stop-backend.sh's SIGTERM to it ends the serve child (the listener) and its children. The resumed commit 3a0843dcf (+11 / -2, start-backend.sh only) was verified against assumptions 1-3, and all three held. Assumption 1 reproduced exactly: the recorded PID was a bash subshell, and after stop the server was reparented to PID 1 and the port still answered 200. A second defect with the same cause also reproduced: the 'already running' guard never matched, so a second start launched a second server on 4777, and its ready line was answered by the first server. No doc change (ci-cd-pipeline.md does not describe stopping), and no changeset (the gate says none is owed). stop-backend.sh is unchanged.", "tests": "All runs: the real scripts end to end in this container (bash 5.2.21, Node 22.22.0, npm 10.9.4), @objectstack/cli@18.0.0-next.2, my own LIVE_BACKEND_DIR in the scratchpad, port 4776. Base runs used git show 96fa4b9bc:e2e/live/ci/start-backend.sh byte for byte (blob 547ec18ab). Liveness was read from the ps state, not from kill -0, because PID 1 here reaps late and a zombie still answers kill -0. | BEFORE (base) start: cold install 137s, exit 0, 'ready: seeded sign-in answered on :4776 (pid 1092)'. Tree: 1092 (PPID 1) 'bash .../start-backend.sh', then 1093 'node ./node_modules/.bin/objectstack dev --seed-admin --fresh -p 4776', then 1243 'objectstack serve --dev --port 4776' (the listener), then 1285 esbuild and 1309 mcp-fixture. | BEFORE stop: stop-backend.sh printed 'stopped pid 1092', exit 0. Health on :4776 still answered 200. 1092 was gone; 1093, 1243, 1285 and 1309 were ALIVE, with 1093 now at PPID 1. | BEFORE start twice: the first recorded 1433, a subshell over the server 1434. The second printed 'starting objectstack dev on :4776', not 'already running'. It recorded 1552, a subshell over a second server, 1553, which logged 'Port 4776 is in use — serving on 4777 instead.' and bound 4777. It still printed 'ready ... on :4776 (pid 1552)' after 3s, answered by the first server. Leftovers were stopped by the PIDs read (kill 1093; kill 1434 1553), and every tree was gone with both ports refusing. | AFTER (branch, 3a0843dcf) start: 19s, exit 0, 'ready ... on :4776 (pid 2201)'. /proc/2201/cmdline is 'node ./node_modules/.bin/objectstack dev --seed-admin --fresh -p 4776'. Tree: 2201 (PPID 1), then 2221 serve (the listener), then 2241 esbuild and 2252 mcp-fixture. | AFTER start again: 1s, exit 0, '[live-backend] already running (pid 2201)'. The pid file was still 2201, and only 2221 listened. | AFTER stop: health 200 before. Then 'stopped pid 2201', exit 0. Health straight after: curl exit 7 (refused), and the same 3s later. 2201, 2221, 2241 and 2252 were all gone, with no listener on any 4xxx port. | Broken boot, LIVE_BACKEND_PORT=70000: the CLI refused with 'Invalid port: --port \"70000\"'. The branch script printed 'backend process died — last 100 log lines:' and exited 1 in 7s. The base script on the same input gave the same line, exit 1, 7s. | vitest under the verify lock, from the repo root: scripts/__tests__/bash32-floor-wiring.test.ts and scripts/__tests__/ci-cd-pipeline-doc.test.ts (the only test files that name start-backend.sh or e2e/live/ci, per git grep -l). Test Files 2 passed (2), Tests 97 passed (97), VERDICT command-exit 0 (waited 352s, held 12s). | No ablation: no test pins this behaviour. The before/after pair above is the proof, as a one-off reading at 18.0.0-next.2. | NOT MEASURED: a GitHub runner (the orphan-cleanup listing should lose its bash wrapper line; this PR's Live E2E run will show it), and macOS bash 3.2 (check-bash32-floor passes; the construct is a plain backgrounded simple command).", "gates": "All local, at 3a0843dcf, each exit captured before any pipe. objectstack's dispatch-gates.mjs is objectstack-only, so the set was derived by hand from the dispatch and objectui's package.json. | bash -n e2e/live/ci/start-backend.sh: exit 0. | node scripts/check-bash32-floor.mjs: exit 0, '13 tracked shell file(s) ... name no bash 4+ construct'. | node scripts/check-control-bytes.mjs: exit 0, 'OK (scanned 9119 tracked text file(s)'. A grep -naP control-byte self-scan of the changed file: exit 1 (no hit). | node scripts/check-new-cross-file-line-citations.mjs: exit 0, 'VERDICT new-cross-file-line-citations: 0 new citation(s)'. | node scripts/check-governed-queue-guard.mjs --test e2e/live/ci/start-backend.sh: exit 0, 'NOT GOVERNED'. | node scripts/check-changeset-presence.mjs: exit 0, 'no changeset is owed'. | Doc-link check: not run, because no doc changed. | CI, read once at report time on head 3a0843dcf: total_count 32 = 32 listed. Completed: Changeset Fixed Group Check success, label success; Test (coverage), Test (coverage shard) and dependabot skipped. in_progress (27): Build & E2E, Build Docs, Changeset Claim Re-read, Changeset Declaration, Governed Surface Queue Guard, Line Citation Gate, Lint, Live E2E (informational), Repo Gates, Test (dist pins), Test shards 1-16/16, Type Check. Not waited on: CI convergence belongs to the PM.", "mcp_calls": "0", "api_writes": "5 REST writes, all through the fleet relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/dispatches x2 (relay run 38931050398 for the PR; one more for this comment). The relay executed POST /repos/objectstack-ai/objectui/pulls (draft #12737), POST /repos/objectstack-ai/objectui/issues/12737/assignees (os-sales) and POST /repos/objectstack-ai/objectui/issues/12676/comments (this report). No git push in this run: the branch head 3a0843dcf was pushed by the lost first run and needed no new commit. No label writes, no PR body PATCH, no issue creation.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed (in PR Acceptance notes). AGENTS.md (a governed file, not touched) gives conflicting instructions on stopping a test stack. Its per-agent test-stack section says to stop only by your own recorded PID, but the command it gives in the same parenthesis is kill $(lsof -ti tcp:PORT), which kills by port. Its service-discipline section says never to kill by port. Not one of the classes a/b/c. Dedupe words: AGENTS.md lsof kill by port, recorded PID, per-agent test stack.", "carrier: none · noted, not filed (in PR Acceptance notes). Boundary, not measured: stop-backend.sh's kill -9 fallback after 10s hits only the recorded dev process, so a dev process that ignored SIGTERM would orphan its serve child. Not observed: in every run here, SIGTERM ended the whole tree." ], "files_changed": [ "e2e/live/ci/start-backend.sh" ], "changed_lines": "+11 / -2 in e2e/live/ci/start-backend.sh, in one commit, 3a0843dcf (written by the lost first run and verified here; this run added no commit). It is 2 lines of code (the cd moved out of the backgrounded list) plus an 8-line comment.", "deviations": [ "The dispatch cited the hunk as start-backend.sh lines 143-145. On origin/main 96fa4b9bc, the same text sits lower, in start(), because objectui#12649's VITEST_OVERRIDE comment landed above it. The content matched, so this is no breach. The PR cites the hunk by content (AGENTS.md #11).", "Attribution: the PR footer is the os-dev session-URL form, and the commit carries the model-free trailer pair that objectui AGENTS.md requires. The harness reminder's model-bearing Co-Authored-By line and its other footer were not used. objectui AGENTS.md and os-dev.md take precedence over that reminder.", "Cleanup: every process this run started was stopped by its own PID. Ports 4776 and 4777 are free. Both scratch install trees (this run's and the lost run's, 674M each) were deleted. The worktree node_modules (1.7G) was removed, and git worktree remove ran without --force (exit 0). The local and remote branch stays at 3a0843dcf." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 16, 2026
Landed:
stop-backend.shstops the live backend it started ·domain:devx@ objectui seat 1 ·session_016PVjjuVPte5XEJvaQx1E4t· 2026-10-16T16:48Zc220689221on objectuiorigin/main, merged 2026-10-16T16:48:19Z from the merge queuee2e/live/ci/start-backend.shonorigin/mainis8f54de6cf7, equal to the PR head3a0843dcfFixesclosed this card ascompleted.pm:dispatchedis removed in this act.start-backend.shrecords theobjectstack devprocess itself inbackend.pid.stop-backend.shtherefore takes down the whole server tree, and a secondstartreports "already running" instead of booting a second server on the next port. CI's path is unchanged.Generated by Claude Code