Conversation
teamai init still has two direct pushes to the default branch that hang or are rejected when main is protected (push: No one): 1. The reviewer-config push (teamai.yaml) uses pushRepoDirectly — rejected on protected main, and simple-git's push has no timeout / no GIT_TERMINAL_PROMPT=0 guard, so a missing-credential push hangs indefinitely instead of throwing, stalling init before local config is written. Switch this to autoPushViaMR (branch + MR, already used by other flows) wrapped in withTimeout(30s), non-blocking. 2. The empty-repo skeleton push also uses pushRepoDirectly with no timeout — wrap it in withTimeout(30s) so a hung push can never block init. (Member registration already moved to the teamai-reports orphan branch upstream, so it no longer touches main.) All failures remain non-blocking (warn only): init always completes and writes local config + skills even if a push/MR could not be created. Co-Authored-By: Claude Code <noreply@anthropic.com>
67455be to
cae3759
Compare
Add a Troubleshooting section to README explaining why `teamai init` hangs after "Registered as team member" (protected default branch + push with no timeout), with a no-code MR-based quick fix. Ship the fix as patches/init-push-via-mr-and-timeout.patch so teams can apply it locally before the PR lands. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Address review feedback on PR Tencent#677: 1. createGit now passes simple-git's timeout.block (30s) to every git instance, so a hung git subprocess is killed at the spawn level — a Promise.race only stopped awaiting while the child kept running and held the Node process open. initRepo is also switched to createGit (it used a bare simpleGit() and bypassed the guards). 2. createGit also sets GIT_TERMINAL_PROMPT=0 on every git subprocess: a push with missing credentials now fails fast with a clear error instead of hanging on an invisible prompt (teamai runs git with no tty, so the prompt could never be answered). 3. Member-registration updateReports calls (both team-repo and single-repo paths) are wrapped in withTimeout(30s) as a second-layer await guard on top of the spawn-level kill. 4. All five README language versions now carry the same Troubleshooting section (en/zh-CN/ja/ko/th), with the patch-file reference removed and the internal hostname taken out. 5. Dropped patches/init-push-via-mr-and-timeout.patch (duplicated the PR's own diff — packaging baggage). New tests: createGit factory-argument assertions in git.test.ts and a real-git regression suite (init-hang-regression.test.ts) that pins fast-failure on a protected default branch and the intact happy path. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
| | `teamai doctor` | 诊断配置问题(`--json` 输出 JSON,供 CI、hook 与 agent 消费)| | ||
| | `teamai uninstall` | 移除所有 teamai 资源和 hooks | | ||
|
|
||
| ## 故障排查 |
There was a problem hiding this comment.
This PR should not modify README.
Address second-round review on PR Tencent#677: 1. GIT_TERMINAL_PROMPT=0 was never applied: 'env' is not a SimpleGitOptions constructor field, so simpleGit(options) silently dropped it. simple-git's .env() overloads both REPLACE the whole child environment (dropping PATH/HOME), so neither works for this. Set it process-wide via disableGitTerminalPrompt(), called once at CLI startup (src/index.ts); every git subprocess inherits it. 2. The 30s block timeout was global in createGit, so it could kill legitimate slow clones/fetches/rebases in unrelated commands. Removed from createGit; added createGitForInitPush (timeout.block, configurable via TEAMAI_INIT_PUSH_TIMEOUT_MS, default 30s) used ONLY by init's pushes. pushRepoDirectly / pushRepoBranch / autoPushViaMR / updateReports gain an opts.initPush flag threaded through the branch-worktree chain; init's 4 push call sites pass it. 3. New real-git regression test: a credential-less push to a 401 HTTP remote (with helpers stripped) is killed by the spawn-level timeout instead of hanging. Factory tests assert the timeout is NOT global. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
…leshooting Address third-round review on PR Tencent#677: 1. The spawn-level timeout did not cover reports-worktree initialization. updateImpl called ensureWorktree before reaching the timed git instance, so cold-start ls-remote/fetch/first-push still used ordinary createGit with no subprocess timeout — a first-time init could hang indefinitely. Thread initPush through ensureWorktree (and syncWorktree, remoteBranchExists, createOrphanWorktree) so every git op during a guarded init push uses createGitForInitPush. 2. The README troubleshooting section overstated the fix (claimed every git subprocess gets a 30s timeout; only selected init-push instances do). Rather than reword five translations, drop the section entirely — it was added by this PR and is not essential documentation, which also satisfies the surgical-change rule. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
…T_MS Address round-4 review on PR Tencent#677: 1. withTimeout's 30s was hard-coded and did not honor TEAMAI_INIT_PUSH_TIMEOUT_MS, so the configurable ceiling was ineffective for operations meant to exceed 30s. Export initPushBlockTimeoutMs() from git.ts and use it in all four withTimeout call sites in init.ts, so the await guard and the spawn-level timeout share one configured value. 2. TEAMAI_INIT_PUSH_TIMEOUT_MS (and GIT_TERMINAL_PROMPT) were undocumented. Add an Environment variables subsection to the Configuration Reference in both usage-guide.md and usage-guide.zh-CN.md. Member registration stays on the teamai-reports orphan branch (upstream design — an orphan branch is not the protected default branch). The push is non-blocking and guarded by the spawn-level timeout threaded through worktree setup; a protected reports branch surfaces as a non-blocking warning, not a hang. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
…n tests Address round-5 review on PR Tencent#677: 1. autoPushViaMR for reviewer config shelled out to a provider's pr-create CLI via spawnSync (gh pr create / TGit), which blocks the event loop — withTimeout's timer could never fire, so a stalled MR creation still blocked init. Reviewer config is part of teamai.yaml and belongs on the default branch, not behind an MR, so push it directly with pushRepoDirectly({initPush:true}) instead. This also fixes the round-5 P2: the generic git provider has no PR API, so MR-only routing regressed it — direct push works for unprotected default branches (the common case), and the spawn-level timeout guards the push itself. 2. The protected-branch and happy-path regression tests called pushRepoDirectly without {initPush:true}, so they exercised the ordinary unguarded factory and could not detect removal of the init-specific subprocess timeout. Both now pass {initPush:true}. autoPushViaMR is no longer called from init; it remains for the import flow in push.ts. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Findings
|
…init retries Address round-6 review on PR Tencent#677: 1. Reviewer config goes via autoPushViaMR again (a protected default branch rejects a direct push). The stalled-MR-creation risk from round-5 is fixed at the root: PrCreateOptions gains spawnTimeoutMs, threaded through createPrWithFallback -> autoPushViaMR (uses initPushBlockTimeoutMs) -> github ghExec / tgit gfExec, which pass it to spawnSync/crossSpawn.sync's timeout option. spawnSync blocks the event loop so withTimeout cannot interrupt it; the spawnSync timeout kills the process at the OS level, bounding init for real. 2. updateReports' push/fetch/rebase retry loop could run for several timeout periods (5 retries) even after init's withTimeout fired. Init pushes now bound retries to 1 (INIT_PUSH_MAX_RETRIES): init is non-blocking and retries on the next run, so a stuck remote no longer holds init for 5x the timeout. 3. TEAMAI_INIT_PUSH_TIMEOUT_MS accepted -1 / 100abc. Validate as a finite positive integer, falling back to 30s otherwise. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Findings
|
|
This PR currently has merge conflicts with |
teamai init hard-pushed the reviewer config and the empty-repo skeleton directly to the default branch via
pushRepoDirectly. When the team repo's main branch is protected (push: No one— common for team repos), the direct push is rejected by the server. Worse, simple-git's push had no subprocess timeout and noGIT_TERMINAL_PROMPT=0guard, so when credentials were missing the push hung indefinitely instead of throwing, stalling the entire init before the local config was ever written. Every new member running init would hit this and be stuck with no skills/config.Member registration already lands on the
teamai-reportsorphan branch upstream — it never touches the protected default branch — so this PR does not move member registration to an MR. That is by design: an orphan branch is not the protected default branch, and the reports-branch flow is the upstream-chosen mechanism for member roster updates. This PR adds a spawn-level timeout guard to that path (and to the reviewer-config and skeleton pushes), so even if the reports branch were also protected or unreachable, init cannot hang.What this PR changes:
autoPushViaMR), so a protected default branch no longer rejects it. The provider'spr createCLI runs viaspawnSync/crossSpawn.sync— which blocks the event loop sowithTimeout's timer can never fire — soPrCreateOptions.spawnTimeoutMsis threaded fromautoPushViaMR(usinginitPushBlockTimeoutMs()) throughcreatePrWithFallback→ githubghExec/ tgitgfExec, which pass it tospawnSync'stimeoutoption. That kills the stalledpr createat the OS level, bounding init for real.createGitForInitPushfactory passes simple-git'stimeout.block(default 30s, configurable viaTEAMAI_INIT_PUSH_TIMEOUT_MS), which kills a hung git subprocess at the process level — unlikewithTimeout, aPromise.racethat only stops awaiting while the child keeps running and holds the Node event loop open. This factory is used only by init's push path; the globalcreateGithas no spawn timeout, so legitimate slow clones/fetches/rebases in unrelated commands are unaffected. TheinitPushflag is threaded through the entire reports-worktree chain (updateReports→updateImpl→ensureWorktree/syncWorktree/commitAndPushAt, including cold-startls-remote/fetch/first-push), so a first-time init's worktree setup is also guarded.withTimeouthonors the same configured timeout. ThewithTimeoutcall sites ininit.tsreadinitPushBlockTimeoutMs()(the same valuecreateGitForInitPushand the MR-creationspawnTimeoutMsuse), so the await guard, the git-subprocess kill, and thepr createsubprocess kill all share one configurable ceiling.GIT_TERMINAL_PROMPT=0process-wide. Set once at CLI startup viadisableGitTerminalPrompt()(src/index.ts); every git subprocess inherits it, so a push with missing credentials fails fast with "could not read Username" instead of hanging on an invisible prompt. (Done process-wide rather than via simple-git's.env(): both its overloads replace the child's whole environment — dropping PATH/HOME — so neither is usable for this.)TEAMAI_INIT_PUSH_TIMEOUT_MSvalidated as a finite positive integer; negative, non-numeric, or non-finite values fall back to 30s rather than reaching simple-git.All push/MR failures stay non-blocking (warn only): init always completes and writes local config + skills even if a push could not be created. A protected
teamai-reportsbranch surfaces as a non-blocking warning (the member file is already written to the local worktree and retried on the next run), not a hang.Summary
Fixes the "teamai init hangs forever after registering as team member" failure mode. Member registration stays on the
teamai-reportsorphan branch (upstream design, unchanged destination) but gets a spawn-level 30s block timeout (scoped to init pushes only, configurable viaTEAMAI_INIT_PUSH_TIMEOUT_MS, threaded through worktree setup, retries capped at 1, and shared by the outerwithTimeoutawait guard) plusGIT_TERMINAL_PROMPT=0process-wide, so a hung or credential-less push is killed fast instead of stalling init. The reviewer-config push goes via MR (protected default branch no longer rejects it) with the provider'spr createsubprocess bounded by the same init-push timeout so a stalled MR creation cannot block the event loop. Push failures remain non-blocking: init always completes and writes local config + skills.Type of Change
Test Plan
npx tsc --noEmitpasses — clean, no errors.npm run buildpasses — ESM build success.npx vitest runpasses — 255 test files / 3529 tests, all green, including:src/__tests__/init-hang-regression.test.ts(new, real-git regression for this PR):updatehook rejecting pushes tomain;pushRepoDirectly({initPush:true})fails fast (~0.7s) instead of hanging, and the happy path (push to an unprotected branch) still completes normally — both exercise the guardedcreateGitForInitPushfactory (per round-5 feedback), so removing it would fail the tests.GIT_TERMINAL_PROMPT=0set, is killed by the spawn-level block timeout (~3.5s withTEAMAI_INIT_PUSH_TIMEOUT_MS=3000) instead of blocking forever — proving the subprocess kill (not just the await guard) is what actually ends the hang.src/__tests__/git.test.ts(updated): assertscreateGitdoes not add a global spawn timeout (so unrelated slow git ops survive),createGitForInitPushadds the 30stimeout.block(honoringTEAMAI_INIT_PUSH_TIMEOUT_MS),initPushBlockTimeoutMs()validates negative/non-numeric values and is shared by the await guard, anddisableGitTerminalPromptsetsGIT_TERMINAL_PROMPT=0process-wide while preserving an explicit user override.src/__tests__/git-kind-reports.test.ts/git-kind-learnings.test.ts(existing real-git suites, all green): protected-default-branch repos with realgit push/ worktree / rebase flows — confirms the scoped timeout, prompt guard, and capped init retries do not break any legitimate git operation, including cold-start worktree creation.npm run buildthendist/index.js init .against a real GitHub repo in a cleanHOME): with no credentials the CLI fails fast at the auth step in ~1s (previously this hung indefinitely on an invisible credential prompt).src/utils/git.ts(the shared simple-git factory),src/init.ts,src/utils/branch-worktree.ts, and the github/tgit providercreatePullRequestpaths, and is provider-agnostic by construction (same code path for git/gitlab/github). The full real-git integration suites above exercise it per-path. If maintainers want the 4-agent × 3-provider end-to-end matrix re-run for this specific change, happy to do it — but the guarded factory is exercised identically regardless of agent/provider choice.Related Issues
Notes for Reviewers
pr createsubprocess bounded (per round-5/6 feedback): an earlier revision routed reviewer config throughautoPushViaMR, but round-5 flagged that the provider'spr createruns viaspawnSync(blocks the event loop, sowithTimeoutcan't interrupt it). This is now fixed at the root:PrCreateOptions.spawnTimeoutMsis threaded fromautoPushViaMR(usinginitPushBlockTimeoutMs()) throughcreatePrWithFallback→ githubghExec/ tgitgfExec, which pass it tospawnSync/crossSpawn.sync'stimeoutoption — the stalledpr createis killed at the OS level. (The genericgitprovider'screatePullRequeststill throws — no PR API — andautoPushViaMR's catch surfaces it as a non-blocking warning; the branch is already pushed, so the user can open the MR manually.)commitAndPushAt's 5-retry push/fetch/rebase loop is bounded to 1 during init (INIT_PUSH_MAX_RETRIES), so a stuck remote can no longer hold init for 5× the timeout; a failed init push is non-blocking and retried on the next init.TEAMAI_INIT_PUSH_TIMEOUT_MSvalidated (per round-6 feedback): negative, non-numeric, or non-finite values now fall back to 30s instead of reaching simple-git.teamai-reportsorphan branch upstream — never the protected default branch. This PR adds a spawn-level timeout guard (threaded through worktree setup, retries capped at 1) so it cannot hang.withTimeouthonors the configured timeout (per round-4 feedback): the await-guard call sites ininit.tsread the exportedinitPushBlockTimeoutMs(), the same valuecreateGitForInitPushand the MR-creationspawnTimeoutMsuse.initPushis threaded throughensureWorktree(cold-startls-remote/fetch/first-push),syncWorktree,remoteBranchExists, andcreateOrphanWorktree.timeout.blocklives in a dedicatedcreateGitForInitPushfactory used only by init's push path. The sharedcreateGithas no spawn timeout, so unrelated commands' slow clones/fetches/rebases are not at risk.GIT_TERMINAL_PROMPT=0actually applied (per round-2 feedback): set process-wide at CLI startup; every git subprocess inherits it. A real-git regression test (401 HTTP remote, helpers stripped) pins the fast-failure behavior.TEAMAI_INIT_PUSH_TIMEOUT_MSdocumented (per round-4 feedback): added an Environment variables subsection to the Configuration Reference in bothdocs/usage-guide.mdanddocs/usage-guide.zh-CN.md.patches/init-push-via-mr-and-timeout.patchfile from the first revision is dropped.