Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions linear/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ cd $WORKSPACE/bitpod-tools
python3 linear/scripts/create_linear_issues_from_seed.py
```

Issue creation note (status/state):
- Linear API issue creation is deterministic when you pass a concrete `stateId` (workflow state ID).
- For Product Development (`team=BIT`), `Backlog` stateId is `162716a8-ffa4-43ea-9e0d-c48fdb8054bc` (BIT-442).

`simulate_e2e.py` runs the feature happy-path sequence:
- PR opened -> In Progress
- PR ready for review -> `In Review`
Expand Down
2 changes: 1 addition & 1 deletion linear/docs/process/linear_operating_guide_v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Maintenance update — 2026-04-28:
- Treat this assignee/delegate restriction as a temporary safety rule until Codex is decoupled from the user's personal Linear identity.
- Do not casually change priority, estimate, due date, or milestone when confidence is low.
- Do not create duplicate retroactive issues when an existing issue already owns the scope.
- Temporary Product Development issue-creation workaround: if a new issue lands in `Icebox 🧊` despite being intended for `Backlog`, immediately correct it to `Backlog` using the concrete Backlog status ID `162716a8-ffa4-43ea-9e0d-c48fdb8054bc` and record the evidence. Tracked by BIT-442.
- Temporary Product Development issue-creation workaround (BIT-442): if a new issue lands in `Icebox 🧊` despite being intended for `Backlog`, immediately correct it to `Backlog` using the concrete workflow state ID `162716a8-ffa4-43ea-9e0d-c48fdb8054bc` (Backlog). If a toolchain only accepts IDs, `Icebox 🧊` is `8bc24299-6afe-49e7-a713-42c4ec7f1863`. Record the evidence.

8. Capability degradation handling
- If tool behavior is impaired, stop speculative actions and post minimal verified state.
Expand Down
57 changes: 56 additions & 1 deletion linear/scripts/create_linear_issues_from_seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,46 @@ def resolve_project_id(token: str, project_name: str):
raise SystemExit(f"project name not found: {project_name}")


def create_issue(token: str, team_id: str, project_id: Optional[str], title: str, description: str):
def resolve_state_id(token: str, team_id: str, state_name: str) -> str:
query = """
query TeamStates($id: ID!) {
team(id: $id) {
id
key
name
states {
nodes {
id
name
type
position
}
}
}
}
"""
payload = gql_request(token, query, {"id": team_id})
errors = payload.get("errors")
if errors:
raise SystemExit(json.dumps(errors, indent=2))
nodes = payload.get("data", {}).get("team", {}).get("states", {}).get("nodes", [])
if not nodes:
raise SystemExit(f"no workflow states returned for team_id={team_id}")
for node in nodes:
if node.get("name") == state_name:
return node["id"]
options = ", ".join(sorted({n.get("name", "") for n in nodes if n.get("name")}))
raise SystemExit(f"workflow state not found: {state_name!r}. options: {options}")


def create_issue(
token: str,
team_id: str,
project_id: Optional[str],
title: str,
description: str,
state_id: Optional[str] = None,
):
query = """
mutation CreateIssue($input: IssueCreateInput!) {
issueCreate(input: $input) {
Expand All @@ -95,6 +134,8 @@ def create_issue(token: str, team_id: str, project_id: Optional[str], title: str
}
if project_id:
input_payload["projectId"] = project_id
if state_id:
input_payload["stateId"] = state_id
payload = gql_request(token, query, {"input": input_payload})
errors = payload.get("errors")
if errors:
Expand All @@ -117,6 +158,16 @@ def main():
parser.add_argument("--seed", default=str(DEFAULT_SEED))
parser.add_argument("--team-key", default="BIT")
parser.add_argument("--project-name", default="Taylor01")
parser.add_argument(
"--state-name",
default="Backlog",
help="Workflow state name to use on create (resolved to stateId). Defaults to Backlog.",
)
parser.add_argument(
"--state-id",
default="",
help="Workflow state ID to use on create (overrides --state-name). Recommended when toolchains have name-resolution ambiguity.",
)
parser.add_argument("--live", action="store_true")
args = parser.parse_args()

Expand All @@ -132,13 +183,17 @@ def main():

team_id = resolve_team_id(token, args.team_key)
project_id = resolve_project_id(token, args.project_name)
requested_state_id = args.state_id.strip() or None
if requested_state_id is None:
requested_state_id = resolve_state_id(token, team_id, args.state_name)
for issue in seed["issues"]:
created = create_issue(
token=token,
team_id=team_id,
project_id=project_id,
title=issue["title"],
description=issue["description"],
state_id=requested_state_id,
)
print(f"{created['identifier']}: {created['title']} -> {created['url']}")

Expand Down
Loading